“Wir gelangen nur selten anders als durch Extreme zur Wahrheit – wir müssen den Irrtum – und oft den Unsinn – zuvor erschöpfen, ehe wir uns zu dem schönen Ziele der ruhigen Weisheit hinaufarbeiten.”
Friedrich von Schiller
Die Java Stream API vereinfacht das Arbeiten mit Collections ungemein. Wo früher kompliziert anmutende Schleifenkonstrukte dem Labyrinth-begeisterten Authisten Freude bereiteten, finden sich heute meist elegante Stream Ausdrücke.
Repository.findAll() .filter(Person::hasChildren()) .max(Comparator.comparing(Person::getBirthday)) .orElseThrow();
Der obige Stream-Ausdruck liefert die jüngste Person in der Datenbank, die Kinder hat. Wird keine Person gefunden, dann wird eine NoSuchElementException
geworfen.
Manchmal interessiert man sich für das früheste Geburtstagsdatum und verwendet die Methode min
statt max
. Aber hin und wieder interessieren der früheste und das späteste Datum.
LocalDate firstHoliday = holidays.stream().min(Comparator.naturalOrder()); LocalDate lastHoliday = holidays.stream().max(Comparator.naturalOrder());
In diesem Fall enthält die Liste holidays
nicht die Feiertage eines ganzen Jahres. Sonst wären die Ergebnisse für Deutschland trivial, nämlich Neujahr und der 2. Weihnachtsfeiertag.
Die Liste zweimal zu durchsuchen wirkt nicht sehr elegant. Schöner wäre ein Collector
, der beide Werte gleichzeitig einsammelt.
Extrema<LocalDate> extrema = holidays.stream() .collect(ExtremaCollector.extrema(Comparator.<LocalDate>naturalOrder()));
Das Beispiel verwendet einen selbstgeschriebenen Collector, der eine Instanz vom Typ Extrema
zurückliefert. Die Instanz enthält das kleine und größte Objekt aus dem Stream bzgl. des übergebenen Comparator. In diesem Fall, den Standardvergleich für LocalDate
Instanzen.
Die Extrema
Klasse ist ein generisches Ergebnisklasse für Minimum und Maximum. Da in leeren Streams keine Extrema existieren, liefern die getMin
und getMax
Methoden Optional
Instanzen zurück.
public final class Extrema<T> { private final T min; private final T max; Extrema(T min, T max) { this.min = min; this.max = max; } public Optional<T> getMin() { return Optional.ofNullable(min); } public Optional<T> getMax() { return Optional.ofNullable(max); } }
Der dazugehörige ExtremaCollector
wird, wie die Collector
Instanzen aus dem Beitrag Einsammeln und Portionieren mit Stream Collector, mit der Convenience Methode Collector.of
erzeugt.
public static <T> Collector<T, ?, Extrema<T>> extrema(Comparator<T> comparator) { return Collector.of( () -> new Accu<>(comparator), Accu::add, Accu::merge, Accu::value, CONCURRENT, UNORDERED); }
Der erste Parameter ist ein Supplier
um den Akkumulator Accu
zum Einsammeln von Minimum und Maximumwerten. Der zweite Parameter dient zum Hinzufügen eines neuen Wertes. Ist der neue Wert kleiner als min
, dann ergibt er ein neues Minimum, ist er größer als max
ein neues Maximum. Das Einsammeln von Minimum und Maximum ist auch sehr effizient, weil nur ein oder zwei Vergleiche pro Element im Stream
durchgeführt werden müssen.
void add(T value) { if (min == null) { max = min = value; return; } if (comparator.compare(min, value) > 0) { min = value; } else if (comparator.compare(max, value) < 0) { max = value; } }
Der dritte Parameter dient zum Zusammenführen von verschieden Accu
Instanzen. Bei paralleler Verarbeitung eines Streams können mehrere Accu
Instanzen erzeugt werden, deren Ergebnisse aggregiert werden müssen.
ExtremaCollect<T> merge(ExtremaCollect<T> collector) { if (comparator.compare(min, collector.min) >; 0) { min = collector.min; } if (comparator.compare(max, collector.max)< 0) { max = collector.max; } return this; }
Auch hier ist die Implementierung recht einfach. Der kleinere der beiden min
Werte wird das neue Minimum und der größere der beiden max
Werte, das neue Maximum.
Der vierte Parameter erzeugt das Ergebnis des ExtremaCollector
, indem eine Extrema
Instanz mit den entgültigen Werten für min
und max
erzeugt wird.
Extrema<T> value() { return new Extrema<>(min, max); }
Die Java Stream API ist nicht nur elegant zu verwenden, auch eigene Collector
Ergänzungen sind einfach zu erstellen. Wer sich den ExtremaCollector
anschauen oder vielleicht sogar verwenden möchte, finden ihn als neues Mitglied der Stream Collector Utilities Familie.
Nachtrag
Im Zuge der Ankündigung von JFrog, ihr Repository JCenter zu schließen, ist die Bibliothek als Java 11 Binary nach Maven Central migriert.
<dependency> <groupId>de.schegge</groupId> <artifactId>stream-collector-utilities</artifactId> <version>0.1.3</version> </dependency>