Hvordan tillate nullverdier med Collectors.toMap() i Java
Det er en kjent feil som null
oppføringsverdier fungerer ikke bra med Collectors.toMap()
i Java.
Anta at vi ønsker å konvertere List<Thing> list
til en Map<String, String> newMap
. La oss også si at hver oppføring inneholder en key
og value
felt (begge String
).
Denne bruken av Collectors.toMap()
ville føre til en NullPointerException
hvis getValue()
returnerer noen gang null
.
newMap = list
.stream()
.collect(
Collectors.toMap(
Thing::getKey,
Thing::getValue
)
);
Hvordan kan vi omgå denne feilen og tillate null
verdier i vår Map
oppføringer?
1. Bruker tilpasset Collector
(tillat dupliserte nøkler)
I stedet for Collectors.toMap()
, kan vi bruke kartets put()
funksjon for å legge til nøkkelverdioppføringer.
newMap = list
.stream()
.collect(
HashMap::new,
(map, elem) -> map.put(
elem.getKey(),
elem.getValue()
),
HashMap::putAll
);
Denne metoden bruker collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
.
I motsetning til Collectors.toMap()
, i tilfelle av dupliserte nøkler, vil denne metoden erstatte verdiene, mens Collectors.toMap()
vil kaste en IllegalStateException
.
Metoden ovenfor er strømimplementering av følgende:
Map<String, String> newMap = new HashMap<>();
list.forEach((elem) -> map.put(elem.getKey(), elem.getValue()));
2. Bruker tilpasset Collector
(avvis dupliserte nøkler)
Hvis vi ikke vil godta dupliserte nøkler, som med implementeringen av Collectors.toMap()
, kan vi lage en tilpasset samler toMapOfNullables()
.
Denne funksjonen godtar null
nøkler, null
verdier, og kaster IllegalStateException
med dupliserte nøkler, selv når den originale nøkkelen tilordnes en null
verdi (tilordninger med null
verdier er forskjellige fra de uten tilordning)
public static <T, K, U> Collector<T, ?, Map<K, U>> toMapWithNullables(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper
) {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> {
Map<K, U> map = new LinkedHashMap<>();
list.forEach(item -> {
K key = keyMapper.apply(item);
U value = valueMapper.apply(item);
if (map.containsKey(key)) {
throw new IllegalStateException(
String.format(
"Duplicate key %s (attempted merging values %s and %s)",
key,
map.get(key),
value
)
);
}
map.put(key, value);
});
return map;
}
);
}
Vi kan bruke denne funksjonen akkurat som vi gjør en vanlig Collector
.
newMap = list
.stream()
.collect(
toMapOfNullables(
Thing::getKey,
Thing::getValue
)
);
Denne metoden bruker collect(Collector<? super T,A,R> collector)
.