【问题标题】:How to get the key in Collectors.toMap merge function?如何获取 Collectors.toMap 合并函数中的密钥?
【发布时间】:2017-06-07 08:13:13
【问题描述】:

当在Collectors.toMap() 期间发现重复的键条目时,将调用合并函数(o1, o2)

问题:如何获取导致重复的密钥?

String keyvalp = "test=one\ntest2=two\ntest2=three";

Pattern.compile("\n")
    .splitAsStream(keyval)
    .map(entry -> entry.split("="))
    .collect(Collectors.toMap(
        split -> split[0],
        split -> split[1],
        (o1, o2) -> {
            //TODO how to access the key that caused the duplicate? o1 and o2 are the values only
            //split[0]; //which is the key, cannot be accessed here
        },
    HashMap::new));

在合并函数中,我想根据 key 来决定是取消映射,还是继续使用这些值。

【问题讨论】:

  • 你可以在之后过滤合并的值,合并时真的需要过滤它们吗?
  • 你能举个例子吗?在合并期间,我必须决定取哪个值(o1 或 o2)。必须在 key 上做出决定。但关键并不总是出现两次。只有在某些情况下才必须决定合并。
  • 好的,我明白了。您可以创建单独的 Map 并运行 .map(entry -> entry.split("=")).forEach() 并检查每个条目是否已经在 Map 中。如果没有 - 添加,否则 - 检查决定是否替换。

标签: java java-8 collectors


【解决方案1】:

您需要使用自定义收集器或使用其他方法。

Map<String, String> map = new Hashmap<>();
Pattern.compile("\n")
    .splitAsStream(keyval)
    .map(entry -> entry.split("="))
    .forEach(arr -> map.merge(arr[0], arr[1], (o1, o2) -> /* use arr[0]));

编写自定义收集器相当复杂。您需要一个相似的 TriConsumer(键和两个值),这在 JDK 中没有,这就是为什么我很确定没有使用的内置函数。 ;)

【讨论】:

    【解决方案2】:

    merge 函数没有机会获取 key,这与内置函数相同的问题,当您省略 merge 函数时。

    解决办法是使用不同的toMap实现,不依赖Map.merge

    public static <T, K, V> Collector<T, ?, Map<K,V>>
        toMap(Function<? super T, ? extends K> keyMapper,
              Function<? super T, ? extends V> valueMapper) {
        return Collector.of(HashMap::new,
            (m, t) -> {
                K k = keyMapper.apply(t);
                V v = Objects.requireNonNull(valueMapper.apply(t));
                if(m.putIfAbsent(k, v) != null) throw duplicateKey(k, m.get(k), v);
            },
            (m1, m2) -> {
                m2.forEach((k,v) -> {
                    if(m1.putIfAbsent(k, v)!=null) throw duplicateKey(k, m1.get(k), v);
                });
                return m1;
            });
    }
    private static IllegalStateException duplicateKey(Object k, Object v1, Object v2) {
        return new IllegalStateException("Duplicate key "+k+" (values "+v1+" and "+v2+')');
    }
    

    (这基本上是 Java 9 的 toMap 没有合并函数的实现)

    所以您需要在代码中做的就是重定向toMap 调用并省略合并函数:

    String keyvalp = "test=one\ntest2=two\ntest2=three";
    
    Map<String, String> map = Pattern.compile("\n")
            .splitAsStream(keyvalp)
            .map(entry -> entry.split("="))
            .collect(toMap(split -> split[0], split -> split[1]));
    

    (或ContainingClass.toMap,如果它既不在同一个类中也不在静态导入中)

    收集器像原来的 toMap 收集器一样支持并行处理,尽管在这里它不太可能从并行处理中受益,即使要处理更多元素。

    如果,如果我理解正确,您只想在基于实际键的合并函数中选择旧值或新值,您可以像这样使用键 Predicate 来完成

    public static <T, K, V> Collector<T, ?, Map<K,V>>
        toMap(Function<? super T, ? extends K> keyMapper,
              Function<? super T, ? extends V> valueMapper,
              Predicate<? super K> useOlder) {
        return Collector.of(HashMap::new,
            (m, t) -> {
                K k = keyMapper.apply(t);
                m.merge(k, valueMapper.apply(t), (a,b) -> useOlder.test(k)? a: b);
            },
            (m1, m2) -> {
                m2.forEach((k,v) -> m1.merge(k, v, (a,b) -> useOlder.test(k)? a: b));
                return m1;
            });
    }
    
    Map<String, String> map = Pattern.compile("\n")
            .splitAsStream(keyvalp)
            .map(entry -> entry.split("="))
            .collect(toMap(split -> split[0], split -> split[1], key -> condition));
    

    有几种方法可以自定义这个收集器……

    【讨论】:

    • 太棒了,我在 1/2 之后才来到这里(我显然已经投票了 :))。我什至没有意识到在合并时我可能需要Key...这很有帮助!
    【解决方案3】:

    当然,有一个简单而琐碎的技巧 - 将密钥保存在“key mapper”函数中,并在“merge”函数中获取密钥。因此,代码可能如下所示(假设键为整数):

    final AtomicInteger key = new AtomicInteger(); 
    ...collect( Collectors.toMap( 
       item -> { key.set(item.getKey()); return item.getKey(); }, // key mapper 
       item -> ..., // value mapper
       (v1, v2) -> { log(key.get(), v1, v2); return v1; } // merge function
    );
    

    注意:这不利于并行处理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-29
      • 1970-01-01
      • 2015-06-24
      • 2019-10-12
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 1970-01-01
      相关资源
      最近更新 更多