Maps lässt einen Nullschlüssel zu und List.class.getSuperclass() gibt null zurück. Aber Collectors.groupingBy gibt ein NPE aus, bei Collectors.java, Zeile 907:
K key = Objects.requireNonNull(classifier.apply
Es funktioniert, wenn ich meinen eigenen Collector erstelle, wobei diese Zeile geändert wird in:
K key = classifier.apply
Meine Fragen sind:
1) Das Javadoc von Collectors.groupingBy sagt nicht, dass es keinen Nullschlüssel abbilden sollte. Ist dieses Verhalten aus irgendeinem Grund notwendig?
2) Gibt es eine andere, einfachere Möglichkeit, einen Nullschlüssel zu akzeptieren, ohne einen eigenen Kollektor erstellen zu müssen?
verwandt: stackoverflow.com/questions/24630963/…
– Bernie
13. Juni 2016 um 19:47 Uhr
Ich hatte das gleiche Problem. Dies ist fehlgeschlagen, da groupingBy Objects.requireNonNull für den vom Klassifikator zurückgegebenen Wert ausführt:
Bei der ersten Frage stimme ich skiwi zu, dass es nicht werfen sollte NPE. Ich hoffe, sie werden das ändern (oder es zumindest zum Javadoc hinzufügen). In der Zwischenzeit habe ich mich zur Beantwortung der zweiten Frage entschieden Collectors.toMap Anstatt von Collectors.groupingBy:
Bitte beachten Sie, dass rolfl eine andere, kompliziertere Antwort gegeben hat, mit der Sie Ihren eigenen Karten- und Listenlieferanten angeben können. Ich habe es nicht getestet.
Danke, es hat mir bei einem anderen Bedürfnis geholfen: groupBy und Count, hier ist mein Wrapper: public static <T,R> Map<R,Long> countGroupBy(Collection<T> list, Function<T,R> groupBy){ Collector<T, ?, Map<R, Long>> mapCollectors = Collectors.toMap( groupBy, l->1L, Long::sum); return list.stream().collect(mapCollectors); }
– pdem
1. Juli 2016 um 13:15 Uhr
Ilan M
Verwenden Sie den Filter vor der Gruppierung nach ##
Filtern Sie die Nullinstanzen vor groupingBy heraus.
Die zweite Frage des OPS besagt implizit, dass sie Nullschlüssel akzeptieren möchten, sodass dies seine Frage nicht beantwortet.
– Clint Eastwood
25. Juli 2016 um 4:20 Uhr
Dies kann Teil einer richtigen Antwort sein. Der andere Teil ist, dass Sie dann explizit Null-Elemente in das Ergebnis einfügen können: result.put(null, myList.stream.filter(p -> p.get() == null).collect(Collectors.toList());Irgendwie hässlich, aber vielleicht immer noch bequemer als das Erstellen eines Sammlers.
– Speedstone
14. November 2017 um 14:30 Uhr
Das Problem: Die Lambda-Funktion groupingBy muss zweimal aufgerufen werden. das kann je nach Funktion teuer werden
– Daniel Alder
31. August 2020 um 18:00 Uhr
Das andere Problem ist das groupingBy Es ist nicht garantiert, dass das Ergebnis veränderlich ist. Aber du kannst bestehen mapFactory Parameter und liefert eine veränderliche Karte (wie HashMap::new)
– Kirill Gagarski
6. Februar um 15:07 Uhr
Zu Ihrer ersten Frage aus den Dokumenten:
Es gibt keine Garantien für Typ, Veränderlichkeit, Serialisierbarkeit oder Threadsicherheit der zurückgegebenen Map- oder List-Objekte.
Da nicht alle Map-Implementierungen Nullschlüssel zulassen, haben sie dies wahrscheinlich hinzugefügt, um sie auf die am häufigsten zulässige Definition einer Map zu reduzieren und maximale Flexibilität bei der Auswahl eines Typs zu erhalten.
Zu Ihrer 2. Frage, Sie brauchen nur einen Lieferanten, würde ein Lambda nicht funktionieren? Ich mache mich immer noch mit Java 8 vertraut, vielleicht kann eine klügere Person eine bessere Antwort hinzufügen.
rollfl
Ich dachte, ich würde mir einen Moment Zeit nehmen und versuchen, dieses Problem, das Sie haben, zu verdauen. Ich habe ein SSCE für das zusammengestellt, was ich erwarten würde, wenn ich es manuell machen würde, und was die groupingBy Umsetzung tut es tatsächlich.
Ich denke nicht, dass dies eine Antwort ist, aber es ist eine Frage, warum es ein Problem ist. Wenn Sie möchten, können Sie diesen Code auch hacken, um einen nullfreundlichen Kollektor zu erhalten.
Bearbeiten: Eine generische freundliche Implementierung:
/** groupingByNF - NullFriendly - allows you to specify your own Map and List supplier. */
private static final <T,K> Collector<T,?,Map<K,List<T>>> groupingByNF (
final Supplier<Map<K,List<T>>> mapsupplier,
final Supplier<List<T>> listsupplier,
final Function<? super T,? extends K> classifier) {
BiConsumer<Map<K,List<T>>, T> combiner = (m, v) -> {
K key = classifier.apply(v);
List<T> store = m.get(key);
if (store == null) {
store = listsupplier.get();
m.put(key, store);
}
store.add(v);
};
BinaryOperator<Map<K, List<T>>> finalizer = (left, right) -> {
for (Map.Entry<K, List<T>> me : right.entrySet()) {
List<T> target = left.get(me.getKey());
if (target == null) {
left.put(me.getKey(), me.getValue());
} else {
target.addAll(me.getValue());
}
}
return left;
};
return Collector.of(mapsupplier, combiner, finalizer);
}
/** groupingByNF - NullFriendly - otherwise similar to Java8 Collections.groupingBy */
private static final <T,K> Collector<T,?,Map<K,List<T>>> groupingByNF (Function<? super T,? extends K> classifier) {
return groupingByNF(HashMap::new, ArrayList::new, classifier);
}
Betrachten Sie diesen Code (der Code gruppiert String-Werte basierend auf String.length() (oder null, wenn der Eingabe-String null ist)):
public static void main(String[] args) {
String[] input = {"a", "a", "", null, "b", "ab"};
// How we group the Strings
final Function<String, Integer> classifier = (a) -> {return a != null ? Integer.valueOf(a.length()) : null;};
// Manual implementation of a combiner that accumulates a string value based on the classifier.
// no special handling of null key values.
BiConsumer<Map<Integer,List<String>>, String> combiner = (m, v) -> {
Integer key = classifier.apply(v);
List<String> store = m.get(key);
if (store == null) {
store = new ArrayList<String>();
m.put(key, store);
}
store.add(v);
};
// The finalizer merges two maps together (right into left)
// no special handling of null key values.
BinaryOperator<Map<Integer, List<String>>> finalizer = (left, right) -> {
for (Map.Entry<Integer, List<String>> me : right.entrySet()) {
List<String> target = left.get(me.getKey());
if (target == null) {
left.put(me.getKey(), me.getValue());
} else {
target.addAll(me.getValue());
}
}
return left;
};
// Using a manual collector
Map<Integer, List<String>> manual = Arrays.stream(input).collect(Collector.of(HashMap::new, combiner, finalizer));
System.out.println(manual);
// using the groupingBy collector.
Collector<String, ?, Map<Integer, List<String>>> collector = Collectors.groupingBy(classifier);
Map<Integer, List<String>> result = Arrays.stream(input).collect(collector);
System.out.println(result);
}
Der obige Code erzeugt die Ausgabe:
{0=[], null=[null], 1=[a, a, b], 2=[ab]}
Exception in thread "main" java.lang.NullPointerException: element cannot be mapped to a null key
at java.util.Objects.requireNonNull(Objects.java:228)
at java.util.stream.Collectors.lambda$groupingBy$135(Collectors.java:907)
at java.util.stream.Collectors$$Lambda$10/258952499.accept(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at CollectGroupByNull.main(CollectGroupByNull.java:49)
skiwi
Zunächst einmal verwenden Sie viele Rohobjekte. Das ist keine gute Idee überhauptkonvertieren Sie zuerst Folgendes:
Class zu Class<?>, dh. Anstelle eines Rohtyps ein parametrisierter Typ mit einer unbekannten Klasse.
Anstatt gewaltsam auf a zu werfen HashMapsollten Sie a angeben HashMap zum Sammler.
Zuerst der richtig getippte Code, ohne sich noch um eine NPE zu kümmern:
Hier ersetzen wir die groupingBy die nur einen Klassifikator nimmt, zu einem, der einen Klassifikator, einen Lieferanten und einen Sammler nimmt. Im Wesentlichen ist dies dasselbe wie zuvor, aber jetzt ist es korrekt eingegeben.
Sie haben in der Tat Recht, dass im Javadoc nicht angegeben ist, dass es einen werfen wird NPEund ich denke nicht, dass es eine werfen sollte, da ich jede Karte liefern darf, die ich will, und wenn meine Karte es zulässt null Schlüssel, dann sollte es erlaubt sein.
Ich sehe derzeit keine andere Möglichkeit, es einfacher zu machen, ich werde versuchen, mich eingehender damit zu befassen.
schwarzr1234
Sie können verwenden Stream#collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) stattdessen.
verwandt: stackoverflow.com/questions/24630963/…
– Bernie
13. Juni 2016 um 19:47 Uhr