如何初始化静态Map?

1241

如何在Java中初始化静态Map

方法一:使用静态初始化器
方法二:使用实例化初始化器(匿名子类) 或 其他方法?

每种方法的优缺点是什么?

以下是两种方法的示例:

import java.util.HashMap;
import java.util.Map;

public class Test {
    private static final Map<Integer, String> myMap = new HashMap<>();
    static {
        myMap.put(1, "one");
        myMap.put(2, "two");
    }

    private static final Map<Integer, String> myMap2 = new HashMap<>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}

2
在Java 8中初始化一个Map:https://dev59.com/questions/1nRB5IYBdhLWcg3wyqOo#37384773 - akhil_mittal
2
请不要使用双括号初始化(double brace initialization) - 这是一种hack方法,容易导致内存泄漏和其他问题。 - dimo414
Java 9?如果输入数量<=10,请使用Map.of,否则请使用Map.ofEntries,请参见https://dev59.com/1nRB5IYBdhLWcg3wyqOo#37384773。 - akhil_mittal
43个回答

1

当我有一个具体实现抽象类的情况,该类定义了一个初始化构造函数但没有默认构造函数,但我希望我的子类具有默认构造函数时,我喜欢使用“静态初始化程序”技术。

例如:

public abstract class Shape {

    public static final String COLOR_KEY = "color_key";
    public static final String OPAQUE_KEY = "opaque_key";

    private final String color;
    private final Boolean opaque;

    /**
     * Initializing constructor - note no default constructor.
     *
     * @param properties a collection of Shape properties
     */
    public Shape(Map<String, Object> properties) {
        color = ((String) properties.getOrDefault(COLOR_KEY, "black"));
        opaque = (Boolean) properties.getOrDefault(OPAQUE_KEY, false);
    }

    /**
     * Color property accessor method.
     *
     * @return the color of this Shape
     */
    public String getColor() {
        return color;
    }

    /**
     * Opaque property accessor method.
     *
     * @return true if this Shape is opaque, false otherwise
     */
    public Boolean isOpaque() {
        return opaque;
    }
}

我的具体实现需要一个默认构造函数:

public class SquareShapeImpl extends Shape {

    private static final Map<String, Object> DEFAULT_PROPS = new HashMap<>();

    static {
        DEFAULT_PROPS.put(Shape.COLOR_KEY, "yellow");
        DEFAULT_PROPS.put(Shape.OPAQUE_KEY, false);
    }

    /**
     * Default constructor -- intializes this square to be a translucent yellow
     */
    public SquareShapeImpl() {
        // the static initializer was useful here because the call to 
        // this(...) must be the first statement in this constructor
        // i.e., we can't be mucking around and creating a map here
        this(DEFAULT_PROPS);
    }

    /**
     * Initializing constructor -- create a Square with the given
     * collection of properties.
     *
     * @param props a collection of properties for this SquareShapeImpl
     */
    public SquareShapeImpl(Map<String, Object> props) {
        super(props);
    }
}

然后要使用这个默认构造函数,我们只需要执行:

public class StaticInitDemo {

    public static void main(String[] args) {

        // create a translucent, yellow square...
        Shape defaultSquare = new SquareShapeImpl();

        // etc...
    }
}

1
注意:这个答案实际上属于问题如何以字面量方式直接初始化HashMap?,但由于在撰写本文时该问题被标记为重复项...
在Java 9之前,使用Map.of()(也仅限于10个映射)之前,您可以扩展自己选择的Map实现,例如:
public class InitHashMap<K, V> extends HashMap<K, V>

重新实现HashMap的构造函数:

public InitHashMap() {
    super();
}

public InitHashMap( int initialCapacity, float loadFactor ) {
    super( initialCapacity, loadFactor );
}

public InitHashMap( int initialCapacity ) {
    super( initialCapacity );
}

public InitHashMap( Map<? extends K, ? extends V> map ) {
    super( map );
}

并添加一个额外的构造函数,受Aerthel's answer启发,但使用Object...<K, V>类型是通用的:

public InitHashMap( final Object... keyValuePairs ) {

    if ( keyValuePairs.length % 2 != 0 )
        throw new IllegalArgumentException( "Uneven number of arguments." );

    K key = null;
    int i = -1;

    for ( final Object keyOrValue : keyValuePairs )
        switch ( ++i % 2 ) {
            case 0:  // key
                if ( keyOrValue == null )
                    throw new IllegalArgumentException( "Key[" + (i >>> 1) + "] is <null>." );
                key = (K) keyOrValue;
                continue;
            case 1:  // value
                put( key, (V) keyOrValue );
        }
}

运行

public static void main( final String[] args ) {

    final Map<Integer, String> map = new InitHashMap<>( 1, "First", 2, "Second", 3, "Third" );
    System.out.println( map );
}

输出

{1=First, 2=Second, 3=Third}

您也可以同样扩展Map接口:
public interface InitMap<K, V> extends Map<K, V> {

    static <K, V> Map<K, V> of( final Object... keyValuePairs ) {

        if ( keyValuePairs.length % 2 != 0 )
            throw new IllegalArgumentException( "Uneven number of arguments." );

        final Map<K, V> map = new HashMap<>( keyValuePairs.length >>> 1, .75f );
        K key = null;
        int i = -1;

        for ( final Object keyOrValue : keyValuePairs )
            switch ( ++i % 2 ) {
                case 0: // key
                    if ( keyOrValue == null )
                        throw new IllegalArgumentException( "Key[" + (i >>> 1) + "] is <null>." );
                    key = (K) keyOrValue;
                    continue;
                case 1: // value
                    map.put( key, (V) keyOrValue );
            }
        return map;
    }
}

运行

public static void main( final String[] args ) {

    System.out.println( InitMap.of( 1, "First", 2, "Second", 3, "Third" ) );
}

输出

{1=First, 2=Second, 3=Third}

除了故意让人困惑之外,您为什么选择执行 >>> 1 而不是 % 2 - Frontear
@Frontear 甚至还有不止一个:1)如果编译器/JIT不能相应地优化 %2,我不知道的话,移位操作比数学运算更高效,因为它们在我所知道的任何 CPU 中都是硬件实现的。2)只有极少数情况下可以在高级语言中使用移位操作,因此我会尽可能使用它们来提醒自己和其他读者--它们是存在的。3)由于减少了磨损,它增加了其他运算符的寿命 [当然,这是在开玩笑,但我想让它成为三个:] - Gerold Broser
听起来像是编译器应该关心的事情,不是吗?在那些可以显示更简化操作的地方添加位移操作只会导致代码被误解。 - Frontear
@Frontear 应该是不必须的。从技术角度来看,移位操作比数学运算要简单得多(在0、1及其组合中的位置很重要)。我同意,它们在高级语言程序中并不经常使用。你有没有编写过汇编或机器码?我有,因此这些操作对我来说非常熟悉,如果有机会我会使用它们。如果你不喜欢也不使用它们,那对我来说没问题。你是想说服我放弃我的知识,因为其他人没有这个知识吗? - Gerold Broser

1
这个使用Apache commons-lang的,很可能已经在你的类路径中了:
Map<String, String> collect = Stream.of(
        Pair.of("hello", "world"),
        Pair.of("abc", "123"),
        Pair.of("java", "eight")
).collect(Collectors.toMap(Pair::getKey, Pair::getValue));

1

Java 8 中的流:

    private static final Map<String, TemplateOpts> templates = new HashMap<>();

    static {
        Arrays.stream(new String[][]{
                {CUSTOMER_CSV, "Plantilla cliente", "csv"}
        }).forEach(f -> templates.put(f[0], new TemplateOpts(f[1], f[2])));
    }

它也可以是一个Object[][],在forEach循环中放置任何内容并进行映射


0

这里有一些不错的答案,但我想再提供一个。

创建自己的静态方法来创建和初始化Map。我有自己的CollectionUtils类在一个包中,我跨项目使用其中的各种实用程序,这些实用程序我经常使用,很容易编写,并避免了对某个更大库的依赖。

这是我的newMap方法:

public class CollectionUtils {
    public static Map newMap(Object... keyValuePairs) {
        Map map = new HashMap();
        if ( keyValuePairs.length % 2 == 1 ) throw new IllegalArgumentException("Must have even number of arguments");
        for ( int i=0; i<keyValuePairs.length; i+=2 ) {
            map.put(keyValuePairs[i], keyValuePairs[i + 1]);
        }
        return map;
    }
}

使用方法:

import static CollectionUtils.newMap;
// ...
Map aMap = newMap("key1", 1.23, "key2", 2.34);
Map bMap = newMap(objKey1, objVal1, objKey2, objVal2, objKey3, objVal3);
// etc...

它不使用泛型,但您可以根据需要将地图强制转换为所需类型(只需确保正确地进行类型转换!)

Map<String,Double> aMap = (Map<String,Double>)newMap("key1", 1.23, "key2", 2.34);

0

这里是由abacus-common编写的代码

Map<Integer, String> map = N.asMap(1, "one", 2, "two");
// Or for Immutable map 
ImmutableMap<Integer, String> = ImmutableMap.of(1, "one", 2, "two");

声明:我是abacus-common的开发者。

0
即使有Guava的很好的ImmutableMap类,有时我仍想流畅地构建一个可变的映射表。发现自己想要避免静态块和匿名子类型,当Java 8出现时,我写了一个小库来帮助,叫做Fluent
// simple usage, assuming someMap is a Map<String, String> already declared
Map<String, String> example = new Fluent.HashMap<String, String>()
    .append("key1", "val1")
    .append("key2", "val2")
    .appendAll(someMap);

有了Java 8接口默认值,我可以为所有标准Java Map实现(即HashMap、ConcurrentSkipListMap等)实现Fluent.Map方法,而无需繁琐的重复。

不可修改的映射也很简单。

Map<String, Integer> immutable = new Fluent.LinkedHashMap<String, Integer>()
    .append("one", 1)
    .append("two", 2)
    .append("three", 3)
    .unmodifiable();

请查看https://github.com/alexheretic/fluent获取源代码、文档和示例。

0
第二种方法可以在需要时调用受保护的方法。这对于初始化构造后不可变的类非常有用。

0

我喜欢匿名类语法;它只需要更少的代码。然而,我发现一个主要的缺点是你将无法通过远程调用序列化该对象。你会收到一个关于在远程端找不到匿名类的异常。


你可以使用双括号语法创建地图,然后复制它。 - Tom Hawtin - tackline

0

现在Java 8已经发布,这个问题值得重新审视。我尝试了一下——看起来你可以利用lambda表达式语法来获得一个相当不错且简洁(但类型安全)的映射字面量语法,它看起来像这样:

Map<String,Object> myMap = hashMap(
    bob -> 5,
    TheGimp -> 8,
    incredibleKoolAid -> "James Taylor",
    heyArnold -> new Date()
);

Map<String,Integer> typesafeMap = treeMap(
    a -> 5,
    bee -> 8,
    sea -> 13
    deep -> 21
);

未经测试的示例代码:https://gist.github.com/galdosd/10823529 我很想知道其他人对此的看法(它有点邪恶...)


1
注意:事实证明,在稍后的尝试中...上述内容实际上并不起作用。我尝试了使用几个不同的javac标志,但无法保留用于参数的名称。 - Domingo Ignacio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接