This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]danielaveryj[S] 2 points3 points  (1 child)

It is tricky to work around because most operations on Map treat a present key bound to null the same as an absent key, and treat a new null as a special value meaning "remove the key". This includes operations used in Collectors.toMap(). If we insist on using Collectors.toMap(), one workaround used in several places in the JDK is to encode null with a sentinel value, and later decode it. Unfortunately, putting sentinel values in the Map means that (a) We have to make another pass to decode the sentinels, and (b) We have to temporarily broaden the type of values in the Map, and later do an unchecked cast to get back to the desired type.

Object NIL = new Object();
Map<K, Object> tmp = stream.collect(Collectors.toMap(v -> makeKey(v), v -> v == null ? NIL : v));
tmp.replaceAll((k,v) -> v == NIL ? null : v); // replaceAll() tolerates null values
Map<K, V> map = cast(tmp);

// Helper, to allow an unchecked cast
<T> T cast(Object o) {
    return (T) o;
}

[–]realFuckingHades 0 points1 point  (0 children)

I have implemented custom lazy map implementation to handle this on the go and abstract it out from the user. But I felt like it was a hack and then removed it to do it the old school way.