【问题标题】:How to initialize a map using a lambda?如何使用 lambda 初始化地图?
【发布时间】:2015-12-28 09:23:19
【问题描述】:

我想在单个语句中声明一个完全填充的映射字段,(可能包含多个嵌套语句),如下所示:

private static final Map<Integer,Boolean> map = 
    something-returning-an-unmodifiable-fully-populated-HashMap;

匿名初始化器不会这样做,原因与调用返回新填充映射的函数不会这样做的原因相同:它们需要两个顶级语句:一个用于变量声明,一个用于方法或初始化器.

双花括号({{}})习语可以使用,但它创建了一个扩展 HashMap&lt;&gt; 的全新类,我不喜欢它所代表的开销。

Java 8 的 lambdas 是否提供了更好的方法来实现这一点?

【问题讨论】:

  • 这有什么理由吗?对于Map,写Type variable = expression;Type variable; { variable = expression; } 都没有关系。它将创建完全相同的代码。
  • 这两个单独的语句在语法上没有绑定在一起,因此它们可能通过复制粘贴或意外在它们之间插入其他内容来分隔。我想把它全部放在一个语句中,因为单个语句给人的感觉很好。
  • 即使中间插入了一些语句,也不会破坏或改变语义。对于final 字段,如果有人删除了初始化程序或添加了第二个,编译器会立即大喊大叫。但是,如果您认为这太脆弱了,那么使用方法仍然没有错。然后,调用绑定到变量,当用作初始化器时,方法应该以其创建的名称命名,因此不需要绑定到使用它的变量。
  • @Holger 在所有帐户上都为真。但是假设我们想要一个声明的那种舒适的感觉。
  • 好吧,你有一些解决方案。虽然我不确定它们是否会营造出一种舒适的感觉……

标签: java dictionary collections lambda java-8


【解决方案1】:

如果你想在单个语句中初始化一个Map,你可以使用Collectors.toMap

假设您想构建一个Map&lt;Integer, Boolean&gt; 将整数映射到调用某个函数f 的结果:

private static final Map<Integer,Boolean> MAP = 
        Collections.unmodifiableMap(IntStream.range(0, 1000)
                                             .boxed()
                                             .collect(Collectors.toMap(i -> i, i -> f(i))));

private static final boolean f(int i) {
    return Math.random() * 100 > i;
}

如果您想使用“静态”已知值对其进行初始化,例如您的答案中的示例,您可以像这样滥用 Stream API:

private static final Map<Integer, Boolean> MAP = 
       Stream.of(new Object[] { 1, false }, new Object[] { 2, true })
             .collect(Collectors.toMap(s -> (int) s[0], s -> (boolean) s[1]));

请注意,这是一个真正的滥用,我个人永远不会使用它:如果你想用已知的静态值构造一个 Map,那么使用 Streams 没有任何好处,你会更好关闭以使用静态初始化程序。

【讨论】:

  • 很好的解决方案,但是,这仅在可以计算值的情况下才有效。如果它是一个任意值的枚举列表,需要映射到一个键(不是整数)怎么办?
  • 我正要问和@Chii一样的问题。
  • @Chii 你能举个例子吗?
  • @MikeNakis 已编辑,但我并不以此为荣
  • 是的,现在我们有了解决方案,我们可以搜索一个它可以解决的问题……
【解决方案2】:

Google Guava's Maps class 为此提供了一些不错的实用方法。 此外,还有ImmutableMap class 及其静态方法。 看看:

    ImmutableMap.of(key1, value1, key2, value2, ...);
    Maps.uniqueIndex(values, keyExtractor);
    Maps.toMap(keys, valueMapper);

【讨论】:

    【解决方案3】:

    如果您真的想在单个语句中初始化地图,您可以编写自定义构建器并在您的项目中使用它。像这样的:

    public class MapBuilder<K, V> {
        private final Map<K, V> map;
    
        private MapBuilder(Map<K, V> map) {
            this.map = map;
        }
    
        public MapBuilder<K, V> put(K key, V value) {
            if(map == null)
                throw new IllegalStateException();
            map.put(key, value);
            return this;
        }
    
        public MapBuilder<K, V> put(Map<? extends K, ? extends V> other) {
            if(map == null)
                throw new IllegalStateException();
            map.putAll(other);
            return this;
        }
    
        public Map<K, V> build() {
            Map<K, V> m = map;
            map = null;
            return Collections.unmodifiableMap(m);
        }
    
        public static <K, V> MapBuilder<K, V> unordered() {
            return new MapBuilder<>(new HashMap<>());
        }
    
        public static <K, V> MapBuilder<K, V> ordered() {
            return new MapBuilder<>(new LinkedHashMap<>());
        }
    
        public static <K extends Comparable<K>, V> MapBuilder<K, V> sorted() {
            return new MapBuilder<>(new TreeMap<>());
        }
    
        public static <K, V> MapBuilder<K, V> sorted(Comparator<K> comparator) {
            return new MapBuilder<>(new TreeMap<>(comparator));
        }
    }
    

    使用示例:

    Map<Integer, Boolean> map = MapBuilder.<Integer, Boolean>unordered()
        .put(1, true).put(2, false).build();
    

    这也适用于 Java-7。

    附带说明一下,我们可能会在 Java-9 中看到类似 Map.of(1, true, 2, false) 的内容。详情请见JDK-8048330

    【讨论】:

    • 你真的应该在构造函数中检查null,而不是在每个put
    • @Alexander,没有。在调用.build() 后,必须进行空检查以防止使用构建器。构造函数中的空检查对此毫无用处。由于构造函数是私有的,我们完全控制不传递 null 给它。
    • "在调用 .build() 后,必须进行空检查以防止使用构建器" 为什么限制只构建一次?我可以想象我想要构建一系列地图的场景(特别是用于测试),每个地图都添加一个新的 k/v 对
    • @Alexander,由于此构建器创建可变地图,创建单个地图后新添加的元素将修改以前由build() 调用返回的地图。在build() 之后使用builder 时,您将需要额外的代码来复制地图,这不会降低只需要一个地图的常见情况的性能(即,您不能只在build() 中复制地图)。这是可行的,但有点复杂,对于这个答案来说是不必要的。
    • 啊。我犯了两个关键错误:一个,我忘记了Collections.unmodifiableMap 只是一个包装器,它不执行复制。其次,我最近对 ​​Swift 的深入了解让我被宠坏了,其中字典是具有写时复制的值类型
    【解决方案4】:

    这里是如何在 Java 8 中使用 lambda 在单个语句中实现字段初始值设定项。

    private static final Map<Integer,Boolean> map =
            ((Supplier<Map<Integer,Boolean>>)() -> {
                Map<Integer,Boolean> mutableMap = new HashMap<>();
                mutableMap.put( 1, false );
                mutableMap.put( 2, true );
                return Collections.unmodifiableMap( mutableMap );
            }).get();
    

    Java 9 解决方案:

    private static final Map<Integer,Boolean> map = Map.of( 1, false, 2, true );
    

    如果您有超过 10 个条目,Map.of() 将不起作用,所以您需要这个:

    private static final Map<Integer,Boolean> map = Map.ofEntries( 
            Map.entry( 1, false ), 
            Map.entry( 2, true ), 
            Map.entry( 3, false ), 
            Map.entry( 4, true ), 
            Map.entry( 5, false ), 
            Map.entry( 6, true ), 
            Map.entry( 7, false ), 
            Map.entry( 8, true ), 
            Map.entry( 9, false ), 
            Map.entry( 10, true ), 
            Map.entry( 11, false ) );
    

    【讨论】:

    • 这种扭曲的代码与简单的初始化程序相比有什么优势?顺便说一句,你不需要创建自己的界面,你可以使用Supplier
    猜你喜欢
    • 2013-10-16
    • 1970-01-01
    • 1970-01-01
    • 2010-10-05
    • 1970-01-01
    • 2021-10-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多