【问题标题】:How to directly initialize a HashMap (in a literal way)?如何直接初始化 HashMap(以字面方式)?
【发布时间】:2016-09-14 17:06:24
【问题描述】:

有没有像这样初始化 Java HashMap 的方法?:

Map<String,String> test = 
    new HashMap<String, String>{"test":"test","test":"test"};

正确的语法是什么?我没有发现任何关于此的信息。这可能吗?我正在寻找最短/最快的方法将一些“最终/静态”值放入地图中,这些值永远不会改变并且在创建地图时提前知道。

【问题讨论】:

标签: java dictionary collections initialization


【解决方案1】:

所有版本

如果您碰巧只需要一个条目:Collections.singletonMap("key", "value")

对于 Java 版本 9 或更高版本:

是的,这现在是可能的。在 Java 9 中,添加了几个工厂方法来简化地图的创建:

// this works for up to 10 elements:
Map<String, String> test1 = Map.of(
    "a", "b",
    "c", "d"
);

// this works for any number of elements:
import static java.util.Map.entry;    
Map<String, String> test2 = Map.ofEntries(
    entry("a", "b"),
    entry("c", "d")
);

在上面的示例中,testtest2 都是相同的,只是表示 Map 的方式不同。 Map.of 方法最多为地图中的十个元素定义,而Map.ofEntries 方法则没有这样的限制。

请注意,在这种情况下,生成的地图将是不可变的地图。如果您希望地图是可变的,您可以再次复制它,例如使用mutableMap = new HashMap&lt;&gt;(Map.of("a", "b"));

(另见JEP 269Javadoc

Java 版本 8 以下:

不,您必须手动添加所有元素。您可以在匿名子类中使用初始化器来使语法更短:

Map<String, String> myMap = new HashMap<String, String>() {{
    put("a", "b");
    put("c", "d");
}};

但是,匿名子类在某些情况下可能会引入不需要的行为。这包括例如:

  • 它会生成一个额外的类,该类会增加内存消耗、磁盘空间消耗和启动时间
  • 在非静态方法的情况下:它包含对调用创建方法的对象的引用。这意味着在创建的映射对象仍然被引用时,不能对外部类的对象进行垃圾回收,从而阻塞额外的内存

使用函数进行初始化还可以让您在初始化程序中生成地图,但避免了令人讨厌的副作用:

Map<String, String> myMap = createMap();

private static Map<String, String> createMap() {
    Map<String,String> myMap = new HashMap<String,String>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    return myMap;
}

【讨论】:

  • 如果你想初始化函数中的元素,这将不起作用...
  • @Michael:是的,如果你想使用一个函数而不是你不能使用一个非函数。但你为什么要这样做?
  • 对于需要包含单个条目的地图的情况,Collections.singletonMap() :)
  • 现在稳定的Java 9已经发布,我更喜欢this link for Javadoc。 +1 因为少了一个依赖!
  • Java 9 entry 记录在哪里?
【解决方案2】:

这是一种方式。

Map<String, String> h = new HashMap<String, String>() {{
    put("a","b");
}};

但是,您应该小心并确保您理解上面的代码(它创建了一个继承自 HashMap 的新类)。因此,您应该在这里阅读更多内容: http://www.c2.com/cgi/wiki?DoubleBraceInitialization ,或者干脆使用番石榴:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);

ImmutableMap.of 最多可用于 5 个条目。否则,请使用构建器:source

【讨论】:

  • 它可以工作,但它很丑陋并且具有用户在执行之前应该了解的无形副作用 - 例如,当场生成整个匿名类。
  • 是的,这就是我写的关于小心的方式,并给出了描述的链接。
  • 很好的链接。该链接中对GreencoddsTenthRuleOfProgramming 的引用值得一读。
  • 你能添加 "as ImmutableMap.builder.put("k1","v1").put("k2","v2").build()" 因为 "of" 方法是最多限制5对?
【解决方案3】:

如果您允许第三方库,您可以使用GuavaImmutableMap 来实现类似文字的简洁:

Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");

这最多适用于5 key/value pairs,否则你可以使用它的builder

Map<String, String> test = ImmutableMap.<String, String>builder()
    .put("k1", "v1")
    .put("k2", "v2")
    ...
    .build();


  • 请注意,Guava 的 ImmutableMap 实现不同于 Java 的 HashMap 实现(最值得注意的是,它是不可变的,并且不允许空键/值)
  • 有关更多信息,请参阅 Guava 在其immutable collection types 上的用户指南文章

【讨论】:

  • 另外,番石榴有 ImmutableMap.builder.put("k1","v1").put("k2","v2").build();
  • ImmutableMap 与 HashMap 不同,因为它会在 null 值上失败,而 map HashMap 不会。
  • 只是为了帮助可能面临此问题的其他人。您必须键入构建器以使其成为 Map,如下所示:Map test = ImmutableMap.builder().put("k1", "v1")。 put("k2", "v2").build();
  • 这太棒了 Jens!
【解决方案4】:

没有直接的方法可以做到这一点 - Java 没有 Map 文字(但是 - 我认为它们是为 Java 8 提出的)。

有些人是这样的:

Map<String,String> test = new HashMap<String, String>(){{
       put("test","test"); put("test","test");}};

这会创建一个 HashMap 的匿名子类,它的实例初始化器会放置这些值。 (顺便说一句,一个地图不能包含两次相同的值,您的第二次 put 将覆盖第一个。我将在下一个示例中使用不同的值。)

正常的方式是这样的(对于局部变量):

Map<String,String> test = new HashMap<String, String>();
test.put("test","test");
test.put("test1","test2");

如果您的 test 映射是实例变量,请将初始化放在构造函数或实例初始化程序中:

Map<String,String> test = new HashMap<String, String>();
{
    test.put("test","test");
    test.put("test1","test2");
}

如果您的 test 映射是类变量,请将初始化放在静态初始化程序中:

static Map<String,String> test = new HashMap<String, String>();
static {
    test.put("test","test");
    test.put("test1","test2");
}

如果你希望你的地图永远不会改变,你应该在初始化之后用Collections.unmodifiableMap(...)包裹你的地图。您也可以在静态初始化程序中执行此操作:

static Map<String,String> test;
{
    Map<String,String> temp = new HashMap<String, String>();
    temp.put("test","test");
    temp.put("test1","test2");
    test = Collections.unmodifiableMap(temp);
}

(我不确定您现在是否可以将test 设为最终...尝试并在此处报告。)

【讨论】:

    【解决方案5】:
    Map<String,String> test = new HashMap<String, String>()
    {
        {
            put(key1, value1);
            put(key2, value2);
        }
    };
    

    【讨论】:

    • 简单明了。我认为这与扩展评论部分将是最好的答案。
    • 有一些需要注意的内存问题。 blog.jooq.org/2014/12/08/…
    • @Amalgovinus 基本上,通过创建一个新的子类,您将HashMap 中的类型参数硬编码到这个子类中。这只有在您实际提供它们时才有效。 (对于一个新的(空的)HashMap,类型参数是不相关的。)
    • 我喜欢它的简洁性,但它会创建不必要的匿名类并且存在此处描述的问题:c2.com/cgi/wiki?DoubleBraceInitialization
    • @hello_its_me: 因为它和stackoverflow.com/a/6802512/1386911 答案相同,只是格式不同。在这种情况下,这种扩展格式在紧凑格式之上没有额外的可读性价值。
    【解决方案6】:

    另一种方法,使用普通的 Java 7 类和可变参数:使用以下方法创建一个类 HashMapBuilder

    public static HashMap<String, String> build(String... data){
        HashMap<String, String> result = new HashMap<String, String>();
    
        if(data.length % 2 != 0) 
            throw new IllegalArgumentException("Odd number of arguments");      
    
        String key = null;
        Integer step = -1;
    
        for(String value : data){
            step++;
            switch(step % 2){
            case 0: 
                if(value == null)
                    throw new IllegalArgumentException("Null key value"); 
                key = value;
                continue;
            case 1:             
                result.put(key, value);
                break;
            }
        }
    
        return result;
    }
    

    使用这样的方法:

    HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
    

    【讨论】:

    • 我写了一个受你启发的答案:stackoverflow.com/questions/507602/…
    • 另一个从未提及但可读的 Apache Utils 解决方案,使用以前的 Java 版本: MapUtils.putAll(new HashMap(), new Object[] { "My key", “我的价值”,...
    • 一个优雅且易于理解的示例! Apache Utils 也很棒。
    【解决方案7】:

    JAVA 8

    在普通的 java 8 中,您还可以使用 Streams/Collectors 来完成这项工作。

    Map<String, String> myMap = Stream.of(
             new SimpleEntry<>("key1", "value1"),
             new SimpleEntry<>("key2", "value2"),
             new SimpleEntry<>("key3", "value3"))
            .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));
    

    这具有不创建匿名类的优点。

    请注意,导入是:

    import static java.util.stream.Collectors.toMap;
    import java.util.AbstractMap.SimpleEntry;
    

    当然,正如其他答案中所述,在 java 9 及以后的版本中,您有更简单的方法来做同样的事情。

    【讨论】:

    • 如果你使用的是 apache commons,你也可以使用 Pair.of() 代替 new SimpleEntry&lt;&gt; 然后 Pair::getKeyPair::getValue
    【解决方案8】:

    tl;博士

    在 Java 9 及更高版本中使用 Map.of… 方法。

    Map< String , String > animalSounds =
        Map.of(
            "dog"  , "bark" ,   // key , value
            "cat"  , "meow" ,   // key , value
            "bird" , "chirp"    // key , value
        )
    ;
    

    Map.of

    Java 9 添加了一系列 Map.of 静态方法来满足您的需求:使用文字语法实例化不可变的 Map

    映射(条目的集合)是不可变的,因此您无法在实例化后添加或删除条目。此外,每个条目的键和值都是不可变的,无法更改。 See the Javadoc 用于其他规则,例如不允许 NULL,不允许重复键,以及映射的迭代顺序是任意的。

    让我们看看这些方法,使用一些示例数据来绘制我们希望在这一天工作的人的星期几的地图。

    Person alice = new Person( "Alice" );
    Person bob = new Person( "Bob" );
    Person carol = new Person( "Carol" );
    

    Map.of()

    Map.of 创建一个空的Map。不可修改,因此您无法添加条目。这是一个这样的地图的例子,没有任何条目。

    Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();
    

    dailyWorkerEmpty.toString():{}

    Map.of( … )

    Map.of( k , v , k , v , …) 是几种采用 1 到 10 个键值对的方法。这是两个条目的示例。

    Map < DayOfWeek, Person > weekendWorker = 
            Map.of( 
                DayOfWeek.SATURDAY , alice ,     // key , value
                DayOfWeek.SUNDAY , bob           // key , value
            )
    ;
    

    weekendWorker.toString(): {SUNDAY=Person{ name='Bob' }, SATURDAY=Person{ name='Alice' }}

    Map.ofEntries( … )

    Map.ofEntries( Map.Entry , … ) 接受任意数量的实现Map.Entry 接口的对象。 Java 捆绑了两个实现该接口的类,一个是可变的,另一个是不可变的:AbstractMap.SimpleEntryAbstractMap.SimpleImmutableEntry。但是我们不需要指定一个具体的类。我们只需要调用Map.entry( k , v )方法,传递我们的key和value,我们就可以得到一个实现Map.Entry接口的类的对象。

    Map < DayOfWeek, Person > weekdayWorker = Map.ofEntries(
            Map.entry( DayOfWeek.MONDAY , alice ) ,            // Call to `Map.entry` method returns an object implementing `Map.Entry`. 
            Map.entry( DayOfWeek.TUESDAY , bob ) ,
            Map.entry( DayOfWeek.WEDNESDAY , bob ) ,
            Map.entry( DayOfWeek.THURSDAY , carol ) ,
            Map.entry( DayOfWeek.FRIDAY , carol )
    );
    

    weekdayWorker.toString(): {WEDNESDAY=Person{ name='Bob' }, TUESDAY=Person{ name='Bob' }, THURSDAY=Person{ name='Carol' }, FRIDAY=Person{ name=' Carol' }, MONDAY=Person{ name='Alice' }}

    Map.copyOf

    Java 10 添加了方法Map.copyOf。传递现有地图,取回该地图的不可变副本。

    注意事项

    请注意,通过Map.of 生成的地图的迭代器顺序是保证的。条目具有任意顺序。不要根据看到的顺序编写代码,因为文档警告说顺序可能会发生变化。

    请注意,所有这些Map.of… 方法都返回一个未指定类Map。底层的具体类甚至可能因 Java 的一个版本而异。这种匿名性使 Java 可以从各种实现中进行选择,无论哪种方式最适合您的特定数据。例如,如果您的密钥来自 enum,Java 可能会在后台使用 EnumMap

    【讨论】:

      【解决方案9】:

      我们可以使用以下方式初始化hashmap:

      1. HashMap 使用构造函数

        Map hashMap = new HashMap();

         hashMap.put("hcl", "amit");
        
         hashMap.put("tcs","ravi");
        

      Hashmap 有四种不同类型的构造函数,所以我们可以根据我们的初始化它 要求。现在使用 HashMap(int initialCapacity) 构造函数

          Map<String, String> hashMap = new HashMap<String, String>(3);
          hashMap.put("virat", "cricket");
          hashMap.put("amit","football");
      

      参考:How to create HashMap

      1. 使用集合的单例 HashMaps

          Map<String, String> immutableMap = Collections.singletonMap("rohit", 
               "cricket");
        
      2. 使用集合的空 HashMaps

          Map<String, String> emptyMap = Collections.emptyMap();
        
      3. 创建HashMap的匿名子类

           Map<String, String> map = new HashMap<String, String>() {{
             put("hcl", "amit");
             put("tcs","ravi");
             put("wipro","anmol");
            }};
        

      【讨论】:

        【解决方案10】:

        我们可以使用具有 SimpleEntry 的 AbstractMap 类,它允许创建不可变的地图

        Map<String, String> map5 = Stream.of(
            new AbstractMap.SimpleEntry<>("Sakshi","java"),
            new AbstractMap.SimpleEntry<>("fine","python")
            ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
            
            System.out.println(map5.get("Sakshi"));
            map5.put("Shiva", "Javascript");
            System.out.println(map5.get("Shiva"));// here we can add multiple entries.
        

        【讨论】:

          【解决方案11】:

          您可以通过 2 种简单的方法轻松创建自己的 Map.of(仅在 Java 9 及更高版本中可用)方法

          使用一定数量的参数来制作

          例子

          public <K,V> Map<K,V> mapOf(K k1, V v1, K k2, V v2 /* perhaps more parameters */) {
              return new HashMap<K, V>() {{
                put(k1, v1);
                put(k2,  v2);
                // etc...
              }};
          }
          

          使用列表制作

          您也可以使用列表来实现这一点,而不是为特定的一组参数创建很多方法。

          例子

          public <K, V> Map<K, V> mapOf(List<K> keys, List<V> values) {
             if(keys.size() != values.size()) {
                  throw new IndexOutOfBoundsException("amount of keys and values is not equal");
              }
          
              return new HashMap<K, V>() {{
                  IntStream.range(0, keys.size()).forEach(index -> put(keys.get(index), values.get(index)));
              }};
          }
          

          注意 不建议对所有内容都使用它,因为每次使用它都会生成一个匿名类。

          【讨论】:

            【解决方案12】:

            如果只需要放置一个键值对,可以使用 Collections.singletonMap(key, value);

            【讨论】:

              【解决方案13】:

              使用 Java 8 或更低版本

              您可以使用静态块来初始化带有一些值的地图。示例:

              public static Map<String,String> test = new HashMap<String, String>
              
              static {
                  test.put("test","test");
                  test.put("test1","test");
              }
              

              使用 Java 9 或更高版本

              您可以使用 Map.of() 方法在声明时使用一些值初始化地图。示例:

              public static Map<String,String> test = Map.of("test","test","test1","test");
              

              【讨论】:

                【解决方案14】:

                简单的方法:

                public static Map<String, String> mapWithValues(String...values) {
                    Map<String, String> map = new HashMap<String, String>();
                    
                    for (int x = 0; x < values.length; x = x+2) {
                        map.put(values[x], values[x+1]);
                    }
                    
                    return map;
                }
                

                【讨论】:

                • 这不只是最流行的答案中描述的不受欢迎的 Java9 之前的旧方法的变体吗?
                【解决方案15】:

                如果它是一个实例变量,那么实例初始化块肯定是要走的路,特别是如果你不能使用Map.of(),因为你需要不同类型的映射。

                但是,如果您感觉活泼,可以使用 Java 8 Supplier(不推荐)。

                private final Map<String,Runnable> games = ((Supplier<Map<String,Runnable>>)() -> {
                  Map<String,Runnable> map = new LinkedHashMap<>();
                
                  map.put("solarus",this::playSolarus);
                  map.put("lichess",this::playLichess);
                
                  return map;
                }).get();
                

                或者制作你自己的功能界面(我觉得不错):

                @FunctionalInterface
                public interface MapMaker<M> {
                  static <M extends Map<K,V>,K,V> M make(M map,MapMaker<M> maker) {
                    maker.build(map);
                    return map;
                  }
                
                  void build(M map);
                }
                
                // Can use LinkedHashMap!
                private final Map<String,Runnable> games = MapMaker.make(
                    new LinkedHashMap<>(),(map) -> {
                      map.put("solarus",this::playSolarus);
                      map.put("lichess",this::playLichess);
                    });
                

                【讨论】:

                  【解决方案16】:

                  我们使用一个简单的实用程序类以流畅的方式初始化地图:

                  Map<String, String> map = MapInit
                      .init("key1", "value1")
                      .put("key2", "value2")
                      .put("key3", "value3")
                      .getMap();
                  

                  实用程序类不受键和值类型的限制,也不受条目数量或结果 Map 类型的限制。

                  实用程序类如下所示:

                  public class MapInit<K, V, T extends Map<K, V>> {
                      private final T map;
                  
                  
                      private MapInit(final T map) {
                          this.map = map;
                      }
                  
                      public T getMap() {
                          return this.map;
                      }
                  
                      public static <K, V> MapInit<K, V, HashMap<K, V>> init(final K key, final V value) {
                          return init(HashMap::new, key, value);
                      }
                  
                      public static <K, V, T extends Map<K, V>> MapInit<K, V, T> init(final Supplier<T> mapSupplier, final K key, final V value) {
                          return new MapInit<>(mapSupplier.get()) //
                                  .put(key, value);
                      }
                  
                      public MapInit<K, V, T> put(final K key, final V value) {
                          this.map.put(key, value);
                          return this;
                      }
                  }
                  

                  【讨论】:

                    【解决方案17】:

                    我想对 Johnny Willer 的回答做一个简短的警告。

                    Collectors.toMap 依赖于Map.merge 并且不期望空值,因此它会抛出一个NullPointerException,正如此错误报告中所述:https://bugs.openjdk.java.net/browse/JDK-8148463

                    另外,如果一个键出现多次,默认的Collectors.toMap 会抛出一个IllegalStateException

                    在 Java 8 上使用构建器语法获取具有空值的映射的另一种方法是编写自定义收集器由 HashMap 支持(因为它确实允许空值):

                    Map<String, String> myMap = Stream.of(
                             new SimpleEntry<>("key1", "value1"),
                             new SimpleEntry<>("key2", (String) null),
                             new SimpleEntry<>("key3", "value3"),
                             new SimpleEntry<>("key1", "value1updated"))
                            .collect(HashMap::new,
                                    (map, entry) -> map.put(entry.getKey(),
                                                            entry.getValue()),
                                    HashMap::putAll);
                    

                    【讨论】:

                      【解决方案18】:

                      不幸的是,如果键和值的类型不同,则使用可变参数不是很合理,因为您必须使用 Object... 并完全失去类型安全性。如果你总是想创建例如Map&lt;String, String&gt;,当然 toMap(String... args) 也是可以的,但不是很漂亮,因为它很容易混淆键和值,并且奇数个参数将无效。

                      您可以创建一个 HashMap 的子类,它具有类似的可链接方法

                      public class ChainableMap<K, V> extends HashMap<K, V> {
                        public ChainableMap<K, V> set(K k, V v) {
                          put(k, v);
                          return this;
                        }
                      }
                      

                      并像new ChainableMap&lt;String, Object&gt;().set("a", 1).set("b", "foo")一样使用它

                      另一种方法是使用通用构建器模式:

                      public class MapBuilder<K, V> {
                        private Map<K, V> mMap = new HashMap<>();
                      
                        public MapBuilder<K, V> put(K k, V v) {
                          mMap.put(k, v);
                          return this;
                        }
                      
                        public Map<K, V> build() {
                          return mMap;
                        }
                      }
                      

                      并像new MapBuilder&lt;String, Object&gt;().put("a", 1).put("b", "foo").build();一样使用它

                      但是,我不时使用的解决方案使用可变参数和 Pair 类:

                      public class Maps {
                        public static <K, V> Map<K, V> of(Pair<K, V>... pairs) {
                          Map<K, V> = new HashMap<>();
                      
                          for (Pair<K, V> pair : pairs) {
                            map.put(pair.first, pair.second);
                          }
                      
                          return map;
                        }
                      }
                      

                      Map&lt;String, Object&gt; map = Maps.of(Pair.create("a", 1), Pair.create("b", "foo");

                      Pair.create() 的冗长让我有点困扰,但这很好用。如果您不介意静态导入,您当然可以创建一个助手:

                      public <K, V> Pair<K, V> p(K k, V v) {
                        return Pair.create(k, v);
                      }
                      

                      Map&lt;String, Object&gt; map = Maps.of(p("a", 1), p("b", "foo");

                      (可以想象使用Map.Entry 而不是Pair,但由于它是一个接口,它需要一个实现类和/或一个辅助工厂方法。它也不是一成不变的,并且包含其他对此任务无用的逻辑。 )

                      【讨论】:

                        【解决方案19】:

                        您可以在 Java 8 中使用 Streams(这是 Set 的示例):

                        @Test
                        public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
                            Set<String> countries = Stream.of("India", "USSR", "USA")
                              .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
                        
                            assertTrue(countries.contains("India"));
                        }
                        

                        参考:https://www.baeldung.com/java-double-brace-initialization

                        【讨论】:

                          【解决方案20】:

                          您可以创建一个方法来初始化地图,如下例所示:

                          Map<String, Integer> initializeMap()
                          {
                            Map<String, Integer> ret = new HashMap<>();
                          
                            //populate ret
                            ...
                          
                            return ret;
                          }
                          
                          //call
                          Map<String, Integer> map = initializeMap();
                          

                          【讨论】:

                            【解决方案21】:

                            以下代码可以在 Java 8 中发挥作用:

                            Map<String, Integer> map = Stream.of(new Object[][] { 
                                 { "data1", 1 }, 
                                 { "data2", 2 }, 
                             }).collect(Collectors.toMap(data -> (String) data[0], data -> (Integer) data[1]));
                            

                            credits:

                            【讨论】:

                              【解决方案22】:

                              Map.of() 似乎是最普遍和最不受限制的。在这里,它会自动处理非对象输入值:

                              List<Map<String, Object> certs = new ArrayList<>(){{ add( Map.of(
                                  "TAG",          Obj1 // Object
                                  "TAGGED_ID",    1L //Long
                                  "DEGREE",       "PARENT" // String
                                  "DATE",         LocalDate.now() // LocalDate
                              ));}};
                              

                              请注意,由静态 Map.of(..) 构造函数创建的映射不允许键和值都为 null

                              【讨论】:

                                猜你喜欢
                                • 2022-12-06
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2020-02-08
                                相关资源
                                最近更新 更多