Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative for throwingMerger in Java 8

I'm implementing own collector that uses merge function. Unfortunately, for some of my cases, I can't reuse the following JDK merger function that thrown IllegalStateException.

java.util.stream.Collectors#throwingMerger

It happens due to the fact that it has private access modifier and access from other(not inner) classes is restricted. However, javadoc says the following:

This can be used to enforce the assumption that the elements being collected are distinct

But, as I see, java doc is out of date. It can't be used. The question is whether JDK provides access to similar functionality for the java developers(similar method, constant etc) or one should write it on their own?

like image 220
dvelopp Avatar asked Dec 11 '22 10:12

dvelopp


1 Answers

The throwingMerger() is implemented as follows

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

You could add a similar method to your code base, but you should be aware of the fundamental problem of that merger: the exception message is incorrect. The first argument to that function is the old value, not the key. The key is not available to this function, so producing an exception message including the duplicate key is impossible for this merge function.

So, since fixing this issue at this place is impossible, it’s good that this function is an implementation detail, so it could be removed for Java 9 without any compatibility constraints.

For providing a reasonable diagnostic, toMap without merge function needs an entirely different implementation than toMap with (non-throwing) merge function, so the toMap and toConcurrentMap collectors without merge function have been entirely rewritten.

A common reason for asking for the throwing merge function, is that there is no toMap overload accepting a map Supplier without the merge function. But since the throwing merger is not going to do the right thing and an entirely different approach is needed when duplicate keys should be rejected, you may use the collector of this answer instead. A slightly improved version of it is

public static <T, K, V, M extends Map<K,V>> Collector<T, ?, M> toMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper,
        Supplier<M> mapSupplier) {

    return Collector.of(mapSupplier,
            (m,t) -> putUnique(m, keyMapper.apply(t),
                                  Objects.requireNonNull(valueMapper.apply(t))),
            (m1,m2) -> {
                if(m1.isEmpty()) return m2;
                if(!m2.isEmpty()) m2.forEach((k,v) -> putUnique(m1, k, v));
                return m1;
            });
}
private static <K, V> void putUnique(Map<K, V> map, K key, V v1){
    V v2 = map.putIfAbsent(key, v1);
    if(v2 != null) throw new IllegalStateException(
        String.format("Duplicate key %s (values %s and %s)", key, v1, v2));
}
like image 134
Holger Avatar answered Dec 20 '22 05:12

Holger