Java 8 Collectors.toMap SortedMap

83
我正在使用Java 8的Lambda表达式,并想使用Collectors toMap方法返回一个SortedMap。我能想到的最好的方法是使用一个虚拟的mergeFunction和将mapSupplier设置为TreeMap::new来调用下面的Collectors toMap方法。
public static <T, K, U, M extends Map<K, U>>
        Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                Function<? super T, ? extends U> valueMapper,
                BinaryOperator<U> mergeFunction,
                Supplier<M> mapSupplier) {
    BiConsumer<M, T> accumulator = (map, element) -> map.merge(keyMapper.apply(element),
            valueMapper.apply(element), mergeFunction);
    return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}

我不想传入一个合并函数,只想要像基本的toMap实现中一样使用 throwingMerger()

public static <T, K, U>
        Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
                Function<? super T, ? extends U> valueMapper) {
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

如何最好地使用Collectors返回一个SortedMap

5个回答

107

我认为你不可能比这更好了:

.collect(Collectors.toMap(keyMapper, valueMapper,
                        (v1,v2) ->{ throw new RuntimeException(String.format("Duplicate key for values %s and %s", v1, v2));},
                        TreeMap::new));

其中 throw 的 lambda 表达式与 throwingMerger() 相同,但我无法直接调用它,因为它是包私有的(当然,您可以始终自己创建一个静态方法,就像 throwingMerger() 一样。)


4
您指定的参数 k 并不是像字母所暗示的那样是,而是用于合并的二进制操作的第一个值。 - antak
1
@antak javadoc 很令人困惑。但我实际上是从 Collectors#throwingMerger 中获取了 Exception message,它对第一个参数说“重复的键”。 - dkatzel
2
是的,异常信息不好。http://mail.openjdk.java.net/pipermail/lambda-dev/2014-April/012005.html - antak
2
这个SO答案:https://dev59.com/bV8e5IYBdhLWcg3wd6Q6 建议采用更简短的方式,只需假设没有重复项(如果有则覆盖)。 - mortensi
6
我将(k,v)更改为(v1,v2),因为lambda参数实际上是两个冲突的值。JDK中的throwingMerger()是错误的。希望你不介意。 :) - Christoffer Hammarström
显示剩余3条评论

8

根据dkatzel的确认,没有一个好的API方法,我选择维护自己的定制Collectors类:

public final class StackOverflowExampleCollectors {

    private StackOverflowExampleCollectors() {
        throw new UnsupportedOperationException();
    }

    private static <T> BinaryOperator<T> throwingMerger() {
        return (u, v) -> {
            throw new IllegalStateException(String.format("Duplicate key %s", u));
        };
    }

    public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends U> valueMapper, Supplier<M> mapSupplier) {
        return Collectors.toMap(keyMapper, valueMapper, throwingMerger(), mapSupplier);
    }

}

2
你应该更改异常消息。你使用了其中一个值(就像JDK所做的那样:https://bugs.openjdk.java.net/browse/JDK-8040892),但是消息表明它是键。可以显示键(http://hg.openjdk.java.net/jdk9/dev/jdk/rev/8b80651ce43f),但这更加复杂,因此也许只需使用`throw new IllegalStateException(String.format("Duplicate key for values %s and %s", u, v));`。 - Martin
嘿@Martin,感谢提供的链接!在这个例子中,我试图直接发布Collectors类的私有throwingMerger方法的代码,以展示我是如何绕过它是私有的这一事实的。我完全理解你的观点,你提供的错误信息更好,但可能会减弱“做完全相同的事情,可见性修饰符正在妨碍”的说法。我可以将您的建议作为编辑添加到底部,并引用您的来源,您觉得怎么样? - Robert Bain

7
似乎没有标准的方法可以在不定义自己的throwingMerger()方法或使用显式lambda的情况下实现此操作。在我的StreamEx库中,我定义了toSortedMap方法,它也使用了我的自己的throwingMerger()方法。

7
似乎有一个疏忽,其中没有包含一个接受Map提供程序的方法签名。 - Robert Bain

5
如果您使用的是guava库,那么您可以使用以下方法:
.collect(ImmutableSortedMap.toImmutableSortedMap(comparator, keyMapper, valueMapper));

生成的地图将是一个SortedMap,并且是不可变的。

5

另一种方法是允许Collectors.toMap()返回它将要返回的任何map,然后将其传递给一个新的TreeMap<>()。

但需要注意的是,这只有在您的“hashCode()+equals()”和“compareTo”是一致的情况下才有效。如果它们不一致,那么HashMap删除的键集与您的TreeMap不同。


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