【问题标题】:builder for HashMapHashMap 的构建器
【发布时间】:2011-11-12 19:12:17
【问题描述】:

Guava 为我们提供了很好的 Java 类型工厂方法,例如 Maps.newHashMap()

但是还有 java Maps 的构建器吗?

HashMap<String,Integer> m = Maps.BuildHashMap.
    put("a",1).
    put("b",2).
    build();

【问题讨论】:

标签: java collections guava


【解决方案1】:

由于 Java 9 Map 接口包含:

  • Map.of(k1,v1, k2,v2, ..)
  • Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ..)

这些工厂方法的局限性在于:

  • 不能将 nulls 保存为键和/或值(如果您需要存储空值,请查看其他答案)
  • 生成不可变地图

如果我们需要可变 map(比如HashMap),我们可以使用它的copy-constructor并让它复制通过Map.of(..)创建的map内容

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );

【讨论】:

  • 请注意,Java 9 方法不允许使用 null 值,这可能是一个问题,具体取决于用例。
  • @JoshM。当我们没有很多值时,可以安全地使用 IMO Map.of(k1,v1, k2,v2, ...)。对于更大数量的值Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...) 为我们提供了更易读的代码,更不容易出错(除非我误解了你)。
  • 你理解得很好。前者对我来说真的很恶心;我拒绝使用它!
【解决方案2】:

Underscore-java 可以构建 hashmap。

Map<String, Object> value = U.objectBuilder()
        .add("firstName", "John")
        .add("lastName", "Smith")
        .add("age", 25)
        .add("address", U.arrayBuilder()
            .add(U.objectBuilder()
                .add("streetAddress", "21 2nd Street")
                .add("city", "New York")
                .add("state", "NY")
                .add("postalCode", "10021")))
        .add("phoneNumber", U.arrayBuilder()
            .add(U.objectBuilder()
                .add("type", "home")
                .add("number", "212 555-1234"))
            .add(U.objectBuilder()
                .add("type", "fax")
                .add("number", "646 555-4567")))
        .build();
    // {firstName=John, lastName=Smith, age=25, address=[{streetAddress=21 2nd Street,
    // city=New York, state=NY, postalCode=10021}], phoneNumber=[{type=home, number=212 555-1234},
    // {type=fax, number=646 555-4567}]}

【讨论】:

    【解决方案3】:

    番石榴中有ImmutableMap.builder()

    【讨论】:

      【解决方案4】:

      你可以在Eclipse Collections使用fluent API:

      Map<String, Integer> map = Maps.mutable.<String, Integer>empty()
              .withKeyValue("a", 1)
              .withKeyValue("b", 2);
      
      Assert.assertEquals(Maps.mutable.with("a", 1, "b", 2), map);
      

      这是一个blog,其中包含更多详细信息和示例。

      注意:我是 Eclipse Collections 的提交者。

      【讨论】:

        【解决方案5】:

        使用 java 8:

        这是Java-9 Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)的一种方法

        public class MapUtil {
            import static java.util.stream.Collectors.toMap;
        
            import java.util.AbstractMap.SimpleEntry;
            import java.util.Map;
            import java.util.Map.Entry;
            import java.util.stream.Stream;
        
            private MapUtil() {}
        
            @SafeVarargs
            public static Map<String, Object> ofEntries(SimpleEntry<String, Object>... values) {
                return Stream.of(values).collect(toMap(Entry::getKey, Entry::getValue));
            }
        
            public static SimpleEntry<String, Object> entry(String key, Object value) {
                return new SimpleEntry<String, Object>(key, value);
            }
        }
        

        如何使用:

        import static your.package.name.MapUtil.*;
        
        import java.util.Map;
        
        Map<String, Object> map = ofEntries(
                entry("id", 1),
                entry("description", "xyz"),
                entry("value", 1.05),
                entry("enable", true)
            );
        

        【讨论】:

          【解决方案6】:

          这是我写的一个

          import java.util.Collections;
          import java.util.HashMap;
          import java.util.Map;
          import java.util.function.Supplier;
          
          public class MapBuilder<K, V> {
          
              private final Map<K, V> map;
          
              /**
               * Create a HashMap builder
               */
              public MapBuilder() {
                  map = new HashMap<>();
              }
          
              /**
               * Create a HashMap builder
               * @param initialCapacity
               */
              public MapBuilder(int initialCapacity) {
                  map = new HashMap<>(initialCapacity);
              }
          
              /**
               * Create a Map builder
               * @param mapFactory
               */
              public MapBuilder(Supplier<Map<K, V>> mapFactory) {
                  map = mapFactory.get();
              }
          
              public MapBuilder<K, V> put(K key, V value) {
                  map.put(key, value);
                  return this;
              }
          
              public Map<K, V> build() {
                  return map;
              }
          
              /**
               * Returns an unmodifiable Map. Strictly speaking, the Map is not immutable because any code with a reference to
               * the builder could mutate it.
               *
               * @return
               */
              public Map<K, V> buildUnmodifiable() {
                  return Collections.unmodifiableMap(map);
              }
          }
          

          你可以这样使用它:

          Map<String, Object> map = new MapBuilder<String, Object>(LinkedHashMap::new)
              .put("event_type", newEvent.getType())
              .put("app_package_name", newEvent.getPackageName())
              .put("activity", newEvent.getActivity())
              .build();
          

          【讨论】:

            【解决方案7】:

            这是一个非常简单的...

            public class FluentHashMap<K, V> extends java.util.HashMap<K, V> {
              public FluentHashMap<K, V> with(K key, V value) {
                put(key, value);
                return this;
              }
            
              public static <K, V> FluentHashMap<K, V> map(K key, V value) {
                return new FluentHashMap<K, V>().with(key, value);
              }
            }
            

            然后

            import static FluentHashMap.map;
            
            HashMap<String, Integer> m = map("a", 1).with("b", 2);
            

            https://gist.github.com/culmat/a3bcc646fa4401641ac6eb01f3719065

            【讨论】:

            • 我喜欢你的简单方法。尤其是现在是 2017 年(现在快 2018 年了!),而且 JDK 中还没有这样的 API
            • 看起来很酷,谢谢。 @MiladNaseri JDK 在其 API 中还没有这样的东西真是太疯狂了,真是太可惜了。
            【解决方案8】:

            这是我一直想要的,尤其是在设置测试夹具时。最后,我决定自己编写一个简单的 fluent 构建器,它可以构建任何 Map 实现 - https://gist.github.com/samshu/b471f5a2925fa9d9b718795d8bbdfe42#file-mapbuilder-java

                /**
                 * @param mapClass Any {@link Map} implementation type. e.g., HashMap.class
                 */
                public static <K, V> MapBuilder<K, V> builder(@SuppressWarnings("rawtypes") Class<? extends Map> mapClass)
                        throws InstantiationException,
                        IllegalAccessException {
                    return new MapBuilder<K, V>(mapClass);
                }
            
                public MapBuilder<K, V> put(K key, V value) {
                    map.put(key, value);
                    return this;
                }
            
                public Map<K, V> build() {
                    return map;
                }
            

            【讨论】:

              【解决方案9】:

              不久前我也有类似的要求。它与 Guava 无关,但您可以执行类似的操作,以便能够使用 fluent 构建器干净地构造 Map

              创建一个扩展 Map 的基类。

              public class FluentHashMap<K, V> extends LinkedHashMap<K, V> {
                  private static final long serialVersionUID = 4857340227048063855L;
              
                  public FluentHashMap() {}
              
                  public FluentHashMap<K, V> delete(Object key) {
                      this.remove(key);
                      return this;
                  }
              }
              

              然后使用适合您需要的方法创建流利的构建器:

              public class ValueMap extends FluentHashMap<String, Object> {
                  private static final long serialVersionUID = 1L;
              
                  public ValueMap() {}
              
                  public ValueMap withValue(String key, String val) {
                      super.put(key, val);
                      return this;
                  }
              
              ... Add withXYZ to suit...
              
              }
              

              然后你可以像这样实现它:

              ValueMap map = new ValueMap()
                    .withValue("key 1", "value 1")
                    .withValue("key 2", "value 2")
                    .withValue("key 3", "value 3")
              

              【讨论】:

                【解决方案10】:

                在我看来,这类似于公认的答案,但更简洁:

                ImmutableMap.of("key1", val1, "key2", val2, "key3", val3);
                

                上述方法有多种变体,它们非常适合制作静态、不变、不可变的地图。

                【讨论】:

                • 我要了一个建筑商。您仅限于少数几个元素。
                • 漂亮又干净,但它确实让我对 Perl 的 => 运算符产生了渴望……这是一种奇怪的感觉。
                【解决方案11】:

                一个简单的地图构建器很容易编写:

                public class Maps {
                
                    public static <Q,W> MapWrapper<Q,W> map(Q q, W w) {
                        return new MapWrapper<Q, W>(q, w);
                    }
                
                    public static final class MapWrapper<Q,W> {
                        private final HashMap<Q,W> map;
                        public MapWrapper(Q q, W w) {
                            map = new HashMap<Q, W>();
                            map.put(q, w);
                        }
                        public MapWrapper<Q,W> map(Q q, W w) {
                            map.put(q, w);
                            return this;
                        }
                        public Map<Q,W> getMap() {
                            return map;
                        }
                    }
                
                    public static void main(String[] args) {
                        Map<String, Integer> map = Maps.map("one", 1).map("two", 2).map("three", 3).getMap();
                        for (Map.Entry<String, Integer> entry : map.entrySet()) {
                            System.out.println(entry.getKey() + " = " + entry.getValue());
                        }
                    }
                }
                

                【讨论】:

                  【解决方案12】:

                  你可以使用:

                  HashMap<String,Integer> m = Maps.newHashMap(
                      ImmutableMap.of("a",1,"b",2)
                  );
                  

                  它没有那么优雅和可读,但确实有效。

                  【讨论】:

                  • Map&lt;String, Integer&gt; map = ImmutableMap.of("a", 1, "b", 2);,更好吗?
                  • 与构建器相同,但数据量有限,因为它是通过重载实现的。如果你只有很少的元素 - 我想这是可取的。
                  【解决方案13】:

                  不完全是构建器,而是使用初始化器:

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

                  【讨论】:

                  • 等等。这不会使map instanceof HashMap 错误吗?看起来不是一个好主意。
                  • @Elazar map.getClass()==HashMap.class 将返回 false。但无论如何,这是一个愚蠢的测试。 HashMap.class.isInstance(map) 应该是首选,这将返回 true。
                  • 这是一个实例初始化器,而不是静态初始化器。对于类中的每个构造函数,它在超级构造函数之后,构造函数主体之前运行。生命周期不是很广为人知,所以我避免使用这个成语。
                  • 这是一个非常糟糕的解决方案,应该避免:stackoverflow.com/a/27521360/3253277
                  • +1,因为这很容易成为我能找到的最“方便”的解决方案(它没有受到ImmutableMap 不支持null 的缺点的影响),但理解这一点很重要阻止它在任何生产代码中使用的重大缺点。但是,我认为在测试或草稿代码中使用它来快速创建地图没有任何缺点。
                  【解决方案14】:

                  HashMaps 没有这样的东西,但是你可以用 builder 创建一个 ImmutableMap:

                  final Map<String, Integer> m = ImmutableMap.<String, Integer>builder().
                        put("a", 1).
                        put("b", 2).
                        build();
                  

                  如果您需要一个可变映射,您可以将它提供给 HashMap 构造函数。

                  final Map<String, Integer> m = Maps.newHashMap(
                      ImmutableMap.<String, Integer>builder().
                          put("a", 1).
                          put("b", 2).
                          build());
                  

                  【讨论】:

                  • ImmutableMap 不支持 null 值。所以这种方法有局限性:您不能将HashMap 中的值设置为null
                  • sean-patrick-floyd 好吧,举一个实际的例子:Spring 的NamedParameterJdbcTemplate 需要由参数名称键入的值的映射。假设我想使用 NamedParameterJdbcTemplate 将列值设置为 null。我没有看到:a)这是一种代码味道; b) 如何在这里使用空对象模式
                  • @vitaly 对此无可争辩
                  • 使用new HashMap Java 构造函数代替静态Maps.newHashMap 方法有什么问题吗?
                  • @CorayThan - Jonik 是正确的,它只是依赖于静态类型推断的捷径。 stackoverflow.com/a/13153812
                  【解决方案15】:

                  HashMap 是可变的;不需要建设者。

                  Map<String, Integer> map = Maps.newHashMap();
                  map.put("a", 1);
                  map.put("b", 2);
                  

                  【讨论】:

                  • 如果你想用它来初始化一个字段怎么办?同一行中的所有逻辑都优于分散在字段和 c'tor 之间的逻辑。
                  • @Elazar:如果你想用编译时已知的特定值初始化一个字段,你通常希望该字段是不可变的,应该使用@987654323 @。如果您真的希望它是可变的,您可以在构造函数或实例初始化程序块或静态初始化程序块(如果它是静态字段)中对其进行初始化。
                  • 呃,应该说ImmutableMap那里明显。
                  • 我不这么认为。我宁愿在同一行定义中看到初始化,然后将它们隐藏在非静态初始化{{init();}} 中(不在构造函数中,因为其他构造函数可能会忘记它)。很高兴它是一种原子动作。如果 map 是 volatile 的,则使用构建器对其进行初始化,以确保它始终为 null 或处于最终状态,永远不会被填满。
                  猜你喜欢
                  • 2021-06-17
                  • 1970-01-01
                  • 2016-10-28
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多