Generics, Lambda-Ausdrücke und Streams in Java

Schreibe Java-Klassen und Methoden, die mit beliebigen Typen arbeiten, während der Compiler jede Verwendung prüft. Übergib dann mit Lambda-Ausdrücken und Methodenreferenzen Verhalten als Wert an Methoden. Sortiere Objekte mit Comparatoren nach beliebigen Regeln und verarbeite Collections mit der Stream-API: Filtere, transformiere, sortiere, zähle, summiere, gruppiere und sammle Daten in wenigen gut lesbaren Schritten. Jedes Beispiel ist ein kleines, vollständiges Stück Code mit seinem genauen Ergebnis. Häufige Compilerfehler und Laufzeitausnahmen werden dabei erklärt.

AnfängerEinheit 11 von 11 in Kurs: Java-Programmierung – Klassen, Vererbung, Collections und Streams

Was du lernst

  1. Verwende Typparameter, um Klassen, Interfaces und Methoden zu schreiben, die mit vielen Typen arbeiten, während der Compiler jede Verwendung prüft.
    • Definiere und verwende generische Klassen
      Deklariere Klassen und Interfaces mit Typparametern, erzeuge Objekte mit Typargumenten und erkläre die Typsicherheit, die dies gegenüber dem Speichern von Object bietet.
    • Schreibe generische Methoden
      Deklariere Methoden mit eigenen Typparametern, rufe sie mit abgeleiteten oder expliziten Typargumenten auf und entscheide, wann eine Methode statt einer Klasse generisch sein sollte.
    • Beschränke Typparameter mit Typschranken und Wildcards
      Verwende beschränkte Typparameter, um Methoden auf einem generischen Typ aufzurufen, erkläre, warum eine List<Integer> keine List<Number> ist, und wähle zwischen ? extends und ? super.
    • Beachte die Einschränkungen durch Typlöschung
      Erkläre, wie der Compiler Typargumente löscht, welche Operationen Generics deshalb nicht erlauben und warum rohe Typen vermieden werden sollten.
  2. Schreibe Lambda-Ausdrücke und Methodenreferenzen für funktionale Interfaces, verwende Javas funktionale Standardinterfaces und übergib Verhalten als Wert an Methoden.
    • Schreibe Lambda-Ausdrücke für funktionale Interfaces
      Erkenne funktionale Interfaces, schreibe Lambda-Ausdrücke in Kurzform und mit Blockrumpf und erkläre, wie der Zieltyp einem Lambda-Ausdruck seine Bedeutung gibt.
    • Verwende die funktionalen Standardinterfaces
      Wähle zwischen Predicate, Function, Consumer, Supplier und verwandten Interfaces, kombiniere sie und übergib sie an Collection-Methoden wie removeIf und replaceAll.
    • Fange Variablen ein und schreibe Methodenreferenzen
      Verwende Variablen aus dem umgebenden Code innerhalb eines Lambda-Ausdrucks, wende die Regel für effektiv finale Variablen an und ersetze einfache Lambda-Ausdrücke durch die vier Arten von Methodenreferenzen.
  3. Sortiere Listen mit Comparatoren nach beliebigen Regeln, von einer selbst geschriebenen compare-Methode bis zu Verkettungen mit comparing, thenComparing und reversed.
    • Schreibe einen Comparator und sortiere damit
      Implementiere den compare-Vertrag korrekt, sortiere Listen und Arrays mit einem Comparator und entscheide zwischen Comparable und Comparator.
    • Kombiniere Comparatoren mit comparing, thenComparing und reversed
      Erstelle Comparatoren aus Sortierschlüsseln, ergänze sekundäre Vergleichskriterien, kehre Ordnungen um, behandle null-Werte und sortiere die Einträge einer Map.
  4. Erstelle Pipelines mit der Stream-API, die Daten filtern, umwandeln, sortieren und reduzieren, und erkläre den Ablauf einer Pipeline.
    • Baue eine Stream-Pipeline auf und verstehe ihren Ablauf
      Erzeuge Streams aus Collections, Arrays und Zahlenbereichen, unterscheide Zwischenoperationen von Terminaloperationen und erkläre die verzögerte Auswertung, die einmalige Verwendung und warum die Quelle unverändert bleibt.
    • Filtere Elemente, wandle sie um und ändere ihre Reihenfolge
      Verwende filter, map, mapToInt, flatMap, distinct, sorted, limit und skip und wechsle zwischen Objekt-Streams und Streams primitiver Werte.
    • Reduziere einen Stream auf ein einzelnes Ergebnis
      Berechne mit Terminaloperationen Anzahlen, Summen, Mittelwerte, Extremwerte und Wahrheitswerte und behandle fehlende Ergebnisse mit Optional.
  5. Sammle Stream-Ergebnisse in Listen, Mengen, Maps und Strings, gruppiere und partitioniere Daten mit Collectoren und entscheide, wann ein Stream oder eine Schleife das bessere Werkzeug ist.
    • Sammle Ergebnisse in Listen, Mengen, Maps und Strings
      Verwende toList, Collectors.toList, toSet, toCollection, joining und toMap und gehe mit nicht modifizierbaren Ergebnissen und doppelten Schlüsseln um.
    • Gruppiere und partitioniere Daten mit Collectoren
      Teile Daten mit groupingBy und partitioningBy in Gruppen auf und fasse jede Gruppe mit einem nachgeschalteten Collector zusammen.
    • Wähle für eine vollständige Aufgabe zwischen Streams und Schleifen
      Löse eine Datenverarbeitungsaufgabe von Anfang bis Ende, vergleiche eine Stream-Lösung mit einer Schleife und halte Pipelines frei von Seiteneffekten.

Lambda-Ausdrücke und Streams in Java einfach erklärt

5 Kapitel

1

Schreibe generische Klassen und Methoden

Generischer Code bewahrt eine nützliche Beziehung zwischen Eingaben, gespeicherten Werten und Ergebnissen. Eine Klasse kann diese Beziehung für ein Objekt festlegen, während eine Methode sie bei jedem Aufruf neu wählen kann. Typschranken und Wildcards ermöglichen es APIs, die benötigten Typen zu akzeptieren, ohne unsichere Schreibzugriffe zu erlauben. Der Compiler prüft diese Zusicherungen, bevor er Typparameter löscht. Das erklärt sowohl die Vorteile von Generics als auch die Laufzeitoperationen, die sie nicht unterstützen können.

1.1 Definiere und verwende generische Klassen

Ein Container sollte dir sagen, was er enthält. Mit einem Typparameter kann eine einzige Klasse Strings, Zahlen oder eigene Objekte aufnehmen, und der Compiler prüft jede Verwendung, bevor das Programm läuft.

Gib einer Klasse einen Typparameter

In class Box<T> ist T ein Typparameter: ein Name für einen Typ, den der aufrufende Code angibt. Verwende ihn für Felder, Methodenparameter und Ergebnisse.

class Box<T> {
    private T value;

    public Box(T value) { this.value = value; }
    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

Der Name des Konstruktors ist Box, ohne <T>. Die Deklaration hinter dem Klassennamen führt T für den Klassenrumpf ein. Erzeuge nun eine Box:

Box<String> b = new Box<>("tea");
String drink = b.get();
b.set("water");
System.out.println(drink);   // tea
System.out.println(b.get()); // water

String ist das Typargument. Für diese Variable gibt get() einen String zurück und set akzeptiert einen String. Beim Lesen ist keine explizite Typumwandlung nötig. Der versuchte Aufruf b.set(42) schlägt bei der Kompilierung fehl: „incompatible types: int cannot be converted to String“. Der Fehler markiert den falschen Schreibzugriff, bevor ein späterer Lesezugriff scheitern kann.

Warum nicht Object speichern?

Ein Object-Feld kann auf einen String oder eine Ganzzahl verweisen. Code, der einen String benötigt, muss das zurückgegebene Objekt explizit umwandeln. Wurde eine Ganzzahl gespeichert, wirft diese Typumwandlung zur Laufzeit eine ClassCastException. Eine Box<String> macht die beabsichtigte Beziehung zum Teil des deklarierten Typs.

Der Diamantoperator <> in new Box<>("tea") veranlasst den Compiler, die Typargumente abzuleiten. Die Prüfungen bleiben erhalten. Lässt du die spitzen Klammern ganz weg, wie in new Box("tea"), verwendest du einen rohen Typ und verlierst Prüfungen im Zusammenhang mit dem Typparameter. Weist du eine Konstruktion mit rohem Typ einer typisierten Variablen zu, entstehen Unchecked-Warnungen; die typisierte Variable setzt ihr eigenes deklariertes Typargument bei späteren Aufrufen weiterhin durch.

Verwende Wrapperklassen und mehrere Parameter

Ein Typargument muss ein Referenztyp sein. Verwende Box<Integer>, nicht Box<int>. Java verpackt das Literal 5 beim Erzeugen von new Box<Integer>(5) als Integer und entpackt es, wenn eine Rechnung einen int benötigt. Enthält n diese Box, ergibt int next = n.get() + 1; den Wert 6.

Zwei unabhängige Typen brauchen zwei Parameter. Ein Pair<K, V> kann ein Feld K first und ein Feld V second haben. Bei einem Pair<String, Integer> geben die Getter einen String und einen Integer zurück. Übliche Namen sind T für einen Typ, E für ein Element, K und V für Schlüssel und Wert und R für ein Ergebnis.

Verwende Typparameter in Interfaces
interface Container<T> {
    void put(T item);
    T take();
}

Eine Klasse kann die Wahl mit class Shelf<T> implements Container<T> offenhalten oder sie mit class NameList implements Container<String> festlegen. List<E> und Comparable<T> verwenden dieselbe Idee. Eine generische Klasse bleibt eine Klasse: Box<String> und Box<Integer> beschreiben unterschiedliche Verwendungen zur Kompilierzeit, keine zwei Klassendeklarationen. Sie können einander nicht zugewiesen werden, weil sie unterschiedliche Elementtypen zusichern.

Wichtige Erkenntnisse

  • Ein Typparameter benennt einen Typ innerhalb einer Deklaration; ein Typargument gibt ihn bei einer Verwendung an.
  • Ein generischer Getter gibt den deklarierten Elementtyp ohne explizite Typumwandlung zurück.
  • Der Diamantoperator erhält die Typprüfungen; bei einem rohen Typ entfallen generische Prüfungen.
  • Verwende Wrapperklassen wie Integer als Typargumente.
  • Klassen und Interfaces können mehr als einen Typparameter haben.

1.2 Schreibe generische Methoden

Manche Operationen benötigen eine Typwahl für einen Aufruf, nicht für die Lebensdauer eines Objekts. Eine generische Methode führt ihren eigenen Typparameter ein und bewahrt eine Beziehung zwischen ihrer Eingabe und ihrem Ergebnis.

Deklariere den Typ vor dem Rückgabetyp
import java.util.List;

class Lists {
    public static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }

    public static void main(String[] args) {
        int number = last(List.of(4, 8, 15));
        String word = Lists.<String>last(List.of("x", "y"));
        System.out.println(number); // 15
        System.out.println(word);   // y
    }
}

Lies die Deklaration in Teilen. static macht sie zu einer Klassenmethode. <T> führt einen Typparameter ein, der zur Methode gehört. Das nächste T ist der Rückgabetyp. List<T> besagt, dass die Eingabeliste und das Ergebnis denselben Elementtyp haben.

Für den ersten Aufruf leitet Java für T den Typ Integer ab. Das Ergebnis kann dann zu int entpackt werden. Im zweiten Aufruf ist <String> ein expliziter Typzeuge, der zwischen Punkt und Methodenname steht. Lists.last(...) würde hier denselben Typ ableiten. Ein Typzeuge benötigt vor dem Punkt einen Empfänger: einen Klassennamen für eine statische Methode oder ein Objekt wie this für eine Instanzmethode.

Diese Methode erwartet eine nicht leere Liste. Bei einer leeren Liste ist size() - 1 gleich -1, sodass get eine IndexOutOfBoundsException wirft. Generics prüfen Typen; sie beweisen nicht, dass ein Index existiert.

Verknüpfe mehrere Typen

Eine generische Methode kann mehrere Parameter einführen. Wenn eine Klasse Pair<K, V> mit Konstruktor und Gettern vorhanden ist, vertauscht diese vollständige Methode die Rollen:

static <K, V> Pair<V, K> swap(Pair<K, V> p) {
    return new Pair<>(p.getSecond(), p.getFirst());
}

Für ein Eingabepaar ("Ada", 36) vom Typ Pair<String, Integer> ist das Ergebnis (36, "Ada") vom Typ Pair<Integer, String>. Die Rückgabedeklaration drückt die Beziehung aus; ein Rückgabetyp Object würde sie verbergen und den aufrufenden Code zu einer expliziten Typumwandlung zwingen.

Eine Methode wie <T> int countEqual(List<T> list, T target) setzt ebenfalls Eingaben zueinander in Beziehung. Ihre Schleife kann jedes Element mit equals mit target vergleichen. Bei ["a", "b", "a", "c", "a"] und "a" ist die Anzahl 3. Die Typinferenz kann für Argumente einen gemeinsamen Obertyp wählen. Daher erzwingt die wiederholte Verwendung von T nicht immer, dass zwei Argumentausdrücke identische deklarierte Typen haben.

Entscheide, zu wem der Typparameter gehört

Eine Box<String> behält ihr Typargument für die Verwendung dieses Objekts. Lists.last wählt für jeden Aufruf ein neues Typargument. Eine gewöhnliche Hilfsklasse kann daher generische statische Methoden enthalten, ohne selbst generisch zu sein.

Eine statische Methode innerhalb von Box<T> kann das T der Klasse nicht verwenden: Die statische Methode gehört zur gemeinsam genutzten Klasse, nicht zu einer einzelnen Box. Deklariere einen eigenen Parameter, zum Beispiel static <E> Box<E> of(E value). Beginnt eine Methodendeklaration in einer gewöhnlichen Klasse mit static T first(...) ohne ein <T> für die Methode, meldet der Compiler, dass er T nicht finden kann. Ergänze die Deklaration vor dem Rückgabetyp; eine Änderung des Rückgabetyps zu Object würde die nützliche Beziehung aufgeben.

Wichtige Erkenntnisse

  • Eine generische Methode deklariert ihre eigenen Typparameter vor ihrem Rückgabetyp.
  • Die Typinferenz findet für jeden Aufruf getrennt statt.
  • Ein Typzeuge steht nach einem Punkt und vor dem Methodennamen.
  • Verwende mehrere Typparameter, wenn Eingabe und Ausgabe mehrere miteinander verknüpfte Rollen haben.
  • Eine statische Methode muss ihren eigenen Typparameter deklarieren.
  • Typsicherheit prüft nicht, ob eine Methode die geforderte nicht leere Eingabe erhält.

1.3 Beschränke Typparameter mit Typschranken und Wildcards

Ein unbeschränktes T unterstützt nur Operationen, die auf Object verfügbar sind. Typschranken ergänzen eine nützliche Zusicherung. Wildcards ermöglichen es einem Parameter, eine Familie generischer Typen zu akzeptieren, ohne vorzugeben, dass alle ihre Elementtypen identisch sind.

Fordere die benötigten Operationen
static <T extends Comparable<T>> T largest(List<T> list) {
    T best = list.get(0);
    for (T x : list) {
        if (x.compareTo(best) > 0) {
            best = x;
        }
    }
    return best;
}

Die Typschranke besagt, dass ein T sich mit einem anderen T vergleichen kann. Ausgehend von 3 ersetzt der Aufruf largest(List.of(3, 11, 7)) den bisher größten Wert durch 11 und lässt ihn bei 7 unverändert. Er gibt 11 zurück. Für ["pear", "apple", "plum"] ist "plum" in natürlicher Ordnung das größte Wort. Eine leere Liste scheitert bei get(0).

extends führt auch für Interfaces Typschranken ein. <T extends Number> macht doubleValue() verfügbar. Mehrere Typschranken verwenden &, wobei eine Klasse, falls vorhanden, an erster Stelle steht: <T extends Number & Comparable<T>>.

Warum List keine List ist

Integer erweitert Number, aber List<Integer> ist kein Untertyp von List<Number>. Generische Typen sind invariant. Wäre diese Zuweisung erlaubt, könnte Code über die Referenz vom Typ List<Number> ein Double hinzufügen; der ursprüngliche Besitzer würde es dann als Integer lesen. Ein Parameter vom Typ List<Number> akzeptiert Implementierungen wie ArrayList<Number>, lehnt aber List<Integer> ab.

Lies mit extends; füge mit super hinzu
static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}

static void addOneToThree(List<? super Integer> target) {
    target.add(1);
    target.add(2);
    target.add(3);
}

? extends Number bedeutet einen unbekannten Typ, der Number oder ein Untertyp davon ist. sum akzeptiert Listen von Ganzzahlen, Double-Werten oder Zahlen. Die Methode liest jedes Element als Number. sum(List.of(1.5, 2.5)) ergibt 4.0. Sie kann 3 nicht hinzufügen: Der unbekannte Elementtyp könnte Double sein. Die Typregeln erlauben das Hinzufügen von null, auch wenn eine bestimmte Liste dies zur Laufzeit ablehnen kann.

? super Integer bedeutet Integer oder einen Obertyp davon. Die zweite Methode kann Ganzzahlen zu einer modifizierbaren List<Integer>, List<Number> oder List<Object> hinzufügen. Ein über diesen Parameter gelesener Wert hat nur den Typ Object: Vorhandene Elemente könnten andere Zahlen oder Objekte sein.

ParameterSichere Operation auf Elementen
List<? extends T>Ein Element als T lesen
List<? super T>Ein T hinzufügen; als Object lesen
List<T>Mit demselben benannten Typ lesen und schreiben
List<?>Als Object lesen; Größe abfragen

Die Merkhilfe lautet PECS-Regel (Producer extends, consumer super). Sie beschreibt die Richtung der Elementübertragung, nicht ob die gesamte Collection unveränderlich ist. Beispielsweise kann das Entfernen von Elementen über eine Sicht mit extends weiterhin möglich sein.

Verwende einen benannten Typparameter, wenn ein Typ mehrere Positionen verknüpfen muss, insbesondere einen Eingabe- und einen Rückgabetyp. Verwende eine Wildcard, wenn nur ein Bereich akzeptierter Eingaben wichtig ist. Collections.copy liest aus List<? extends T> und schreibt in List<? super T>; das Ziel muss bereits genügend Positionen zum Ersetzen haben.

Wichtige Erkenntnisse

  • Typschranken machen die Methoden des als Schranke angegebenen Typs für T verfügbar.
  • Die Invarianz generischer Typen schützt eine Liste vor Schreibzugriffen mit dem falschen Elementtyp.
  • Verwende ? extends T, um Elemente als T zu lesen.
  • Verwende ? super T, um Werte vom Typ T hinzuzufügen; gelesene Werte haben den Typ Object.
  • Eine Wildcard beschränkt Operationen über eine Referenz; sie macht das Objekt nicht unveränderlich.
  • Verwende einen benannten Typparameter, um Eingabe- und Ergebnistypen zu verknüpfen.
  • Collections.copy ersetzt vorhandene Positionen im Ziel.

1.4 Beachte die Einschränkungen durch Typlöschung

Generics geben dem Compiler mehr Informationen, als ein gewöhnlicher Klassentest zur Laufzeit hat. Wenn du verstehst, was nach der Kompilierung erhalten bleibt, erklären sich Warnungen zu rohen Typen und mehrere Operationen, die Java ablehnt.

Was die Typlöschung entfernt

Java prüft Typargumente und ersetzt dann Typparameter durch ihre Typschranken. Ein unbeschränktes T wird zu Object; ein T extends Number wird zu Number. Der Compiler fügt beim Lesen von Werten dort, wo nötig, explizite Typumwandlungen ein. Zur Laufzeit gibt es eine Klasse Box, daher gibt dies true aus:

System.out.println(
    new Box<String>("a").getClass()
    == new Box<Integer>(1).getClass()
);

Die Deklaration von Box kann die generische Klasse mit einem Wertefeld, einem Konstruktor und einem Getter sein. Beide Konstruktionen haben dieselbe Laufzeitklasse, obwohl ihre deklarierten Verwendungen unterschiedliche Prüfungen zur Kompilierzeit haben.

Operationen, die einen bekannten Laufzeittyp benötigen

Innerhalb einer generischen Klasse lässt sich new T() nicht kompilieren. Java weiß nicht, welchen Konstruktor der vom aufrufenden Code angegebene Typ hat. Nimm eine Fabrik entgegen, um den Wert zu erzeugen:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

class Factory {
    static <T> T create(Supplier<T> factory) {
        return factory.get();
    }

    public static void main(String[] args) {
        List<String> words = create(ArrayList::new);
        words.add("ready");
        System.out.println(words); // [ready]
    }
}

Der Supplier stellt das Verhalten zum Erzeugen bereit. Auch new T[10] und new List<Integer>[2] werden als generische Array-Erzeugung abgelehnt. Speichere eine Folge in einer ArrayList<T>, wenn du das Verhalten eines Arrays nicht benötigst.

Für ein Object obj kann obj instanceof List<String> das Element-Typargument nicht sicher prüfen. Verwende obj instanceof List<?>, um zu prüfen, ob es sich um eine Liste handelt, und behandle ihre Elemente dann als unbekannt. Dieser Test weist keinen String-Elementtyp nach.

Gemeinsame statische Mitglieder und Überladungen nach Typlöschung

Ein Feld wie static T shared kann das T der umgebenden Klasse nicht verwenden. Alle Parametrisierungen teilen sich das statische Mitglied. Eine statische Methode kann ein eigenes <E> einführen, unabhängig vom Klassenparameter.

Zwei Überladungen print(Set<String> s) und print(Set<Integer> s) können nicht in einer Klasse nebeneinander bestehen. Beide werden durch die Typlöschung zu einer Methode, die Set entgegennimmt, sodass ein Namenskonflikt entsteht. Gib den Operationen unterschiedliche Namen oder entwirf eine Methode, die das gemeinsame Verhalten behandelt.

Verfolge eine Unchecked-Warnung bis zum fehlerhaften Lesezugriff
List raw = new ArrayList();
raw.add("text");
raw.add(42);
for (Object value : raw) {
    String text = (String) value;
    System.out.println(text);
}

Dies gibt text aus, dann wirft die Typumwandlung von 42 eine ClassCastException. Die Deklaration mit rohem Typ kann eine Meldung zum rohen Typ auslösen; die Aufrufe zum Hinzufügen lösen Unchecked-Warnungen aus, weil generische Prüfungen umgangen wurden. Ersetzt du den rohen Typ durch List<String>, tritt der Fehler bei add(42) zur Kompilierzeit auf. Eine Unchecked-Warnung zeigt eine verlorene Garantie und das Risiko eines Fehlers bei der Ausführung an. Behebe die Verwendung des rohen Typs oder die unsichere Typumwandlung, statt anzunehmen, dass ein späterer Lesezugriff sicher sein wird.

Wichtige Erkenntnisse

  • Die Typlöschung ersetzt Typparameter durch ihre Typschranken oder durch Object, wenn sie unbeschränkt sind.
  • Parametrisierte Verwendungen teilen sich eine Laufzeitklasse.
  • Verwende eine übergebene Fabrik statt new T().
  • Parametertypen nach Typlöschung können generische Überladungen nicht unterscheiden.
2

Übergib Verhalten mit Lambda-Ausdrücken

Eine Methode kann sowohl Verhalten als auch Daten entgegennehmen. Funktionale Interfaces beschreiben die Form dieses Verhaltens, und Lambda-Ausdrücke liefern die auszuführende Arbeit. Die Standardinterfaces geben häufigen Aufgaben klare Namen: prüfen, umwandeln, ausführen und bereitstellen. Eingefangene Variablen ermöglichen es einer Regel, Informationen aus ihrer Umgebung zu verwenden, während Methodenreferenzen vorhandenes Verhalten benennen. Der passende Zieltyp verbindet jedes Syntaxelement mit einem gewöhnlichen Methodenaufruf.

2.1 Schreibe Lambda-Ausdrücke für funktionale Interfaces

Eine Methode kann eine Regel als Argument entgegennehmen. Ein Lambda-Ausdruck formuliert diese Regel dort, wo sie verwendet wird, während ein funktionales Interface Java mitteilt, welche Eingaben die Regel entgegennimmt und was sie zurückgibt.

Eine abstrakte Methode erfüllen

Ein funktionales Interface hat eine zu implementierende abstrakte Methode. Deklarationen, die öffentlichen Instanzmethoden von Object entsprechen, etwa equals(Object), zählen nicht mit. Default-Methoden und statische Methoden fügen keine abstrakten Aufgaben hinzu. Comparator<T> und Runnable sind funktionale Interfaces.

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

class Combine {
    static int combine(int a, int b, Operation op) {
        return op.apply(a, b);
    }

    public static void main(String[] args) {
        Operation add = (a, b) -> a + b;
        System.out.println(add.apply(3, 4)); // 7
        System.out.println(combine(3, 4, (x, y) -> x * y)); // 12
    }
}

Das Interface liefert den Methodennamen apply, die beiden Parametertypen int und den Ergebnistyp int. Der Lambda-Ausdruck liefert die Parameternamen und den Rumpf. Er erstellt eine Implementierung, die über Operation verwendet werden kann; apply wird erst aufgerufen, wenn das Programm die Methode aufruft.

combine übergibt seine beiden Zahlen immer an op. Der aufrufende Code ändert das Verhalten, indem er Addition oder Multiplikation vorgibt. Auf diese Weise erhält auch sort eine Ordnungsregel und filter eine Bedingung.

Die Syntax lesen

Ein Lambda-Ausdruck hat Parameter, -> und danach einen Ausdruck oder einen Block. Bei einem einzelnen Parameter mit abgeleitetem Typ können die Klammern entfallen: w -> w.length() > 5. Bei keinem Parameter ist () nötig, und bei zwei oder mehr Parametern sind Klammern nötig, wie in (a, b) -> a * b. Explizite Typen sind erlaubt: (int a, int b) -> a + b.

Ein Ausdrucksrumpf gibt sein Ergebnis automatisch zurück. Ein Block, der einen Wert liefert, muss ihn zurückgeben:

Operation max = (a, b) -> {
    if (a > b) {
        return a;
    }
    return b;
};
System.out.println(max.apply(3, 9)); // 9

Jeder Pfad durch diesen Block gibt einen int zurück. Geschweifte Klammern um a + b ohne return ergeben keinen gültigen Block, der einen Wert zurückgibt.

Der Zieltyp legt die Bedeutung fest

Der Zieltyp ist das funktionale Interface, das bei einer Zuweisung, einem Argument oder einer Rückgabe erwartet wird. Der Text x -> x * 2 kann zu Function<Integer, Integer> oder UnaryOperator<Integer> passen. Seine Parameter- und Ergebnistypen stammen aus diesem Zieltyp. Object o = () -> 1; schlägt fehl, weil Object keine funktionale Methode vorgibt.

@FunctionalInterface veranlasst den Compiler, die Interface-Definition zu prüfen. Das schützt sinnvollerweise davor, dass später jemand eine zweite unterschiedliche abstrakte Methode hinzufügt. Ohne die Annotation kann ein solches Interface weiterhin deklariert werden, aber ein Lambda-Ausdruck kann seine beiden voneinander unabhängigen Anforderungen nicht implementieren.

Ein Lambda-Ausdruck kann auch mit lokalen Namen in Konflikt geraten. Wenn die umgebende Methode bereits int a deklariert, kann sie in diesem Gültigkeitsbereich keinen Lambda-Parameter namens a deklarieren. Wähle einen anderen Parameternamen. Im Vergleich zu einer anonymen Klasse, die Comparator<String> implementiert, erspart ein Lambda-Ausdruck die wiederholte Klassen- und Methodendeklaration; die Berechnung der Ordnung muss weiterhin den Comparator-Vertrag erfüllen.

Wichtige Erkenntnisse

  • Ein Lambda-Ausdruck liefert die Implementierung der abstrakten Methode eines funktionalen Interfaces.
  • Das Zielinterface bestimmt die Eingabetypen, den Ergebnistyp und den Methodennamen.
  • Ausdrucksrümpfe geben einen Wert direkt zurück; Blöcke, die einen Wert zurückgeben, benötigen return.
  • @FunctionalInterface prüft die Interface-Definition.
  • Ein Lambda-Parameter kann eine umgebende lokale Variable nicht erneut deklarieren.

2.2 Verwende die funktionalen Standardinterfaces

Java hat bereits Namen für die häufigen Formen von Verhalten. Wähle ein Interface anhand der Anzahl der Eingaben und der Art des Ergebnisses und rufe dann seine funktionale Methode auf, um das Verhalten auszuführen.

Eine Form für Eingabe und Ausgabe wählen
InterfaceFunktionale MethodeAufgabe
Predicate<T>boolean test(T t)Eine Bedingung prüfen
Function<T, R>R apply(T t)Einen Wert umwandeln
Consumer<T>void accept(T t)Eine Aktion ausführen
Supplier<T>T get()Ohne Eingabe einen Wert liefern

Diese Interfaces stehen in java.util.function. Für Strings passt w -> w.length() > 5 zu einem Predicate, String::length zu einer Function, die Integer zurückgibt, und System.out::println zu einem Consumer. Ein Supplier wie () -> new ArrayList<String>() erzeugt bei jeder Ausführung von get() eine neue Liste. Eine von filter verwendete Bedingung benötigt Predicate<T>, auch wenn eine Function<T, Boolean> in anderem Code ebenfalls einen booleschen Wert berechnen könnte.

UnaryOperator<T> ist eine Funktion von T auf dasselbe T. BinaryOperator<T> verbindet zwei Werte vom Typ T zu einem T. BiFunction<T, U, R>, BiPredicate<T, U> und BiConsumer<T, U> nehmen zwei Eingaben entgegen. Primitive Spezialisierungen vermeiden das Verpacken: IntPredicate.test(int), IntBinaryOperator.applyAsInt(int, int) und ToIntFunction<T>.applyAsInt(T).

Regeln in einer festgelegten Reihenfolge kombinieren
import java.util.function.Function;
import java.util.function.Predicate;

class Rules {
    public static void main(String[] args) {
        Predicate<String> isLong = w -> w.length() > 5;
        Predicate<String> longB = isLong.and(w -> w.startsWith("b"));
        System.out.println(longB.test("banana")); // true
        System.out.println(isLong.negate().test("kiwi")); // true

        Function<Integer, Integer> twice = x -> x * 2;
        Function<Integer, Integer> inc = x -> x + 1;
        System.out.println(twice.andThen(inc).apply(5)); // 11
        System.out.println(twice.compose(inc).apply(5)); // 12
    }
}

and verlangt beide Bedingungen, or mindestens eine und negate kehrt die Antwort um. Predicate.not(p) negiert ebenfalls ein Predicate. andThen führt zuerst den Empfänger aus; compose führt zuerst sein Argument aus. Hier berechnet die erste Verkettung 10 und dann 11, während die zweite 6 und dann 12 berechnet.

Eine Regel an eine Methode übergeben
static <T> List<T> keep(List<T> list, Predicate<T> test) {
    List<T> kept = new ArrayList<>();
    for (T x : list) {
        if (test.test(x)) kept.add(x);
    }
    return kept;
}

keep(List.of(4, 9, 12, 15), n -> n % 3 == 0) ergibt [9, 12, 15]. Dank T lässt sich die Methode für verschiedene Elementtypen wiederverwenden; das Predicate macht das Auswahlverhalten wiederverwendbar.

Für eine modifizierbare Liste [3, 8, 1, 10, 6] lässt removeIf(n -> n < 5) die Liste [8, 10, 6] übrig. replaceAll(n -> n * 2) ergibt danach [16, 20, 12]. Diese Methoden ändern die Liste; forEach akzeptiert einen Consumer für Aktionen wie die Ausgabe. Kopiere ein Ergebnis von List.of(...) in eine ArrayList, bevor du ändernde Operationen verwendest.

Für Maps nimmt forEach((key, value) -> ...) einen BiConsumer entgegen. merge(word, 1, Integer::sum) speichert 1 für ein neues Wort und erhöht eine vorhandene Anzahl. computeIfAbsent(key, k -> new ArrayList<>()) erzeugt einen Wert für einen fehlenden oder auf null abgebildeten Schlüssel und gibt den resultierenden Wert zurück. Diese Methoden halten die Regel für Abfrage und Aktualisierung zusammen.

Wichtige Erkenntnisse

  • Wähle ein funktionales Interface anhand der Form seiner Eingabe und seines Ergebnisses.
  • andThen führt zuerst den Empfänger aus; compose führt zuerst sein Argument aus.
  • removeIf prüft ein Predicate; replaceAll wendet einen unären Operator an.
  • merge verbindet einen vorhandenen Map-Wert mit einem übergebenen Wert.
  • computeIfAbsent kann einen fehlenden Map-Wert erstellen und speichern.
  • Spezialisierungen für primitive Datentypen arbeiten direkt mit primitiven Werten.

2.3 Fange Variablen ein und schreibe Methodenreferenzen

Ein Lambda-Ausdruck kann Informationen aus seiner umgebenden Methode verwenden. Wenn sein Rumpf nur eine vorhandene Methode aufruft, drückt eine Methodenreferenz dasselbe Verhalten oft direkter aus.

Eine lokale Variable einfangen
int limit = 10;
Predicate<Integer> small = n -> n < limit;
System.out.println(small.test(7)); // true

Der Lambda-Ausdruck verwendet limit durch Einfangen. Eine eingefangene lokale Variable muss final oder effektiv final sein: Ihr wird einmal ein Wert zugewiesen und sie erhält danach keine Neuzuweisung. Fügt man irgendwo nach dieser ersten Zuweisung limit = 20; hinzu, lässt sich das Einfangen nicht mehr kompilieren, selbst wenn die Neuzuweisung vor dem Lambda-Ausdruck steht.

Die Regel gilt für die lokale Variable, nicht für den gesamten Zustand ihres Objekts. Dies lässt sich kompilieren:

StringBuilder log = new StringBuilder();
Runnable action = () -> log.append("ran");
action.run();
System.out.println(log); // ran

log verweist weiterhin auf denselben Builder. Auch das Aktualisieren eines Felds des umgebenden Objekts ist erlaubt, aber versteckte Änderungen können es erschweren, das Verhalten nachzuvollziehen.

Der Versuch, numbers.forEach(n -> total += n) mit einem lokalen int total = 0 zu verwenden, schlägt fehl, weil er die eingefangene Variable ändert. Verwende eine Schleife, wenn die Aufgabe eine Aktualisierung ist, oder berechne ein Ergebnis direkt mit numbers.stream().mapToInt(n -> n).sum().

Die vier Referenzformen lesen

Eine Methodenreferenz verwendet :: ohne Argumentliste. Das funktionale Interface als Zieltyp liefert die Eingaben und das erwartete Ergebnis.

ArtReferenzEntsprechender Lambda-Ausdruck
Statische MethodeInteger::parseInts -> Integer.parseInt(s)
Gebundene InstanzmethodeSystem.out::printlnx -> System.out.println(x)
Ungebundene InstanzmethodeString::lengths -> s.length()
KonstruktorArrayList::new() -> new ArrayList<>()

Bei der gebundenen Referenz steht das Empfängerobjekt bereits fest: der PrintStream, auf den System.out verweist. Bei der ungebundenen Referenz wird das erste Argument des funktionalen Interfaces zum Empfängerobjekt. So entspricht String::compareToIgnoreCase dem Ausdruck (a, b) -> a.compareToIgnoreCase(b): Der erste String ist das Empfängerobjekt und der zweite das Methodenargument.

Die Referenz auf einen Zieltyp abstimmen
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

class References {
    public static void main(String[] args) {
        Function<String, Integer> parse = Integer::parseInt;
        Function<String, Integer> length = String::length;
        Supplier<List<String>> fresh = ArrayList::new;
        System.out.println(parse.apply("42")); // 42
        System.out.println(length.apply("stream")); // 6
        System.out.println(fresh.get()); // []
    }
}

Auch eine Konstruktorreferenz kann Argumente entgegennehmen. Student::new passt zu einer Function<String, Student>, wenn ein passender Konstruktor einen String entgegennimmt. Der Zieltyp bestimmt, welche Konstruktorüberladung ausgewählt wird.

Student::getName verweist auf einen Instanz-Getter für jeden übergebenen Studierenden; ein statischer Getter ist nicht nötig. Schreibe einen Lambda-Ausdruck, wenn zusätzliche Arbeit anfällt, wie in s -> s.getPoints() * 2 oder w -> w.length() > 5. String::length() zu schreiben ist ungültig: Die Referenz benennt Verhalten, ohne es an dieser Stelle aufzurufen.

Wichtige Erkenntnisse

  • Eine eingefangene lokale Variable muss final oder effektiv final sein.
  • Eine finale Referenz kann weiterhin auf ein veränderliches Objekt verweisen.
  • Eine gebundene Referenz legt ihr Empfängerobjekt fest; eine ungebundene Referenz bezieht es aus dem ersten Argument.
  • Konstruktorreferenzen verwenden ClassName::new.
  • Methodenreferenzen haben keine Aufrufklammern.
3

Ordne Objekte mit Comparator

Für dieselben Objekte können mehrere sinnvolle Ordnungen nötig sein: Namen alphabetisch, Punkte absteigend oder Gruppen mit einem sekundären Vergleichskriterium für die Punktzahl. Comparatoren halten diese Regeln von der natürlichen Ordnung der Klasse getrennt. Ein korrekter Vergleich muss die Beziehungen zwischen Vorzeichen und Reihenfolge konsistent einhalten. Erzeugermethoden machen die Schlüssel sichtbar, aber die Position einer Umkehrung und die Behandlung von Gleichständen oder Nullwerten bestimmen weiterhin das Ergebnis.

3.1 Schreibe einen Comparator und sortiere damit

Die natürliche Ordnung eines Objekts ist nicht immer die Ordnung, die eine Aufgabe benötigt. Mit einem separaten Comparator<T> kannst du dieselben Objekte nach unterschiedlichen Regeln sortieren, ohne ihre Klasse zu ändern.

Das Vorzeichen von compare lesen

compare(a, b) gibt eine negative Zahl zurück, wenn a zuerst kommt, null, wenn beide gleich eingeordnet werden, und eine positive Zahl, wenn a nach b kommt. Nur das Vorzeichen zählt. Ein Comparator gibt keinen booleschen Wert zurück.

Comparable<T> gehört zur Klasse und liefert über compareTo ihre natürliche Ordnung. Ein Comparator ist ein separater Wert, der an eine Sortiermethode übergeben wird. Strings haben eine natürliche Zeichenordnung, aber ein Comparator kann sie stattdessen nach Länge sortieren.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class SortWords {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(List.of("ccc", "a", "bb"));
        Comparator<String> byLength =
            (a, b) -> Integer.compare(a.length(), b.length());
        words.sort(byLength);
        System.out.println(words); // [a, bb, ccc]
    }
}

Der Comparator ermittelt die Längen und vergleicht die Zahlen. words.sort ändert die modifizierbare Liste und gibt void zurück; die Methode gibt keine neue Liste zurück. Das Sortieren eines Ergebnisses von List.of(...) wirft eine UnsupportedOperationException, daher erstellt das Beispiel zunächst eine ArrayList-Kopie.

Zahlen sicher vergleichen

Verwende Integer.compare oder Double.compare. Eine Subtraktion kann das Vorzeichen durch Überlauf umkehren: 2_000_000_000 - (-2_000_000_000) ergibt als int den Wert -294.967.296. Dadurch steht die größere Zahl bei aufsteigender Ordnung fälschlich zuerst. Integer.compare(2_000_000_000, -2_000_000_000) gibt korrekt 1 zurück.

Auch die explizite Umwandlung einer Differenz von Double-Werten in int verliert Informationen. (int) (1.2 - 0.7) ist 0, obwohl der erste Wert größer ist. Double.compare(1.2, 0.7) meldet ein positives Ergebnis.

Die Ordnung konsistent halten

Das Vertauschen der Eingaben muss das Vorzeichen des Vergleichs umkehren. Wenn a vor b und b vor c steht, muss a vor c stehen. Werte, deren Vergleich null ergibt, müssen gegenüber jedem anderen Wert gleich eingeordnet werden. Ein Verstoß gegen diese Regeln kann eine falsche Reihenfolge ergeben oder dazu führen, dass die Sortierung eine IllegalArgumentException mit „Comparison method violates its general contract!“ wirft. Gibt man nur 0 oder 1 zurück, lassen sich nicht beide Richtungen ausdrücken.

Javas Listensortierungen und Comparator-basierte Sortierungen von Objekt-Arrays sind stabil: Bei Gleichständen bleibt die ursprüngliche Reihenfolge erhalten. Für die Studierenden Ada 85, Ben 72, Chen 85, Dara 90 und Eli 64 ergibt das Sortieren nach aufsteigender Punktzahl Eli, Ben, Ada, Chen, Dara. Ada bleibt vor Chen, weil beide gleich viele Punkte haben und Ada zuerst vorkam.

Die Beispiele mit Studierenden verwenden diese Klasse und eine modifizierbare Liste:

class Student {
    private final String name;
    private final String group;
    private final int points;

    Student(String name, String group, int points) {
        this.name = name;
        this.group = group;
        this.points = points;
    }
    String getName() { return name; }
    String getGroup() { return group; }
    int getPoints() { return points; }
}
List<Student> students = new ArrayList<>(List.of(
    new Student("Ada", "A", 85), new Student("Ben", "B", 72),
    new Student("Chen", "A", 85), new Student("Dara", "B", 90),
    new Student("Eli", "A", 64)));
students.sort(Comparator.comparingInt(Student::getPoints));
students.forEach(s -> System.out.println(s.getName()));
// Eli, Ben, Ada, Chen, Dara, each on a separate line
Einen Sortieraufruf wählen

list.sort(cmp) und Collections.sort(list, cmp) ändern eine modifizierbare Liste. list.sort(null) fordert die natürliche Ordnung an. Arrays.sort(array, cmp) akzeptiert ein Objekt-Array wie Integer[]; für ein int[] gibt es keine Überladung mit Comparator.

Die natürliche String-Ordnung vergleicht UTF-16-Codeeinheiten. Die Großbuchstaben A–Z stehen vor den Kleinbuchstaben a–z, sodass aus ["banana", "Cherry", "apple"] die Liste [Cherry, apple, banana] wird. String.CASE_INSENSITIVE_ORDER ergibt für diese Wörter [apple, banana, Cherry]. Wähle die Textregel ausdrücklich, wenn die Groß- und Kleinschreibung die Reihenfolge nicht bestimmen soll.

Wichtige Erkenntnisse

  • Ein Comparator gibt ein Vergleichsergebnis mit Vorzeichen zurück, keinen booleschen Wert.
  • Comparable liefert die natürliche Ordnung; ein Comparator liefert eine separate Ordnung.
  • Verwende numerische Vergleichsmethoden, um Überlauf und Abschneiden zu vermeiden.
  • Stabile Sortierungen erhalten bei Gleichständen die ursprüngliche Reihenfolge.
  • Das Sortieren einer Liste ändert eine modifizierbare Liste und gibt void zurück.
  • Arrays primitiver Werte akzeptieren keinen Comparator.

3.2 Kombiniere Comparatoren mit comparing, thenComparing und reversed

Eine Ordnung nach mehreren Schlüsseln lässt sich am leichtesten als Folge von Entscheidungen lesen: Vergleiche den Hauptschlüssel und vergleiche einen zweiten Schlüssel nur dann, wenn der erste Vergleich einen Gleichstand ergibt. Comparator-Erzeugermethoden drücken diese Entscheidungen direkt aus.

Einen Schlüssel extrahieren und Gleichstände auflösen

Für einen Student mit getName(), getGroup() und getPoints() ordnet Comparator.comparing(Student::getName) nach Namen. comparingInt(Student::getPoints) extrahiert einen int, ohne ihn zu verpacken.

Comparator<Student> ranking =
    Comparator.comparingInt(Student::getPoints)
        .reversed()
        .thenComparing(Student::getName);

Dies ordnet hohe Punktzahlen zuerst und bei gleichen Punkten die Namen aufsteigend. Für Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64) ergibt sich Dara, Ada, Chen, Ben, Eli. Der Namensvergleich wird nur bei einem Punktegleichstand ausgeführt.

reversed auf der richtigen Ebene platzieren

reversed() kehrt alles zuvor Erstellte um. Würde man die Rangfolge nach thenComparing umkehren, würden auch die Namen innerhalb jedes Punktegleichstands umgekehrt. Um Gruppen aufsteigend und Punkte innerhalb jeder Gruppe absteigend zu ordnen, kehre nur den Punkte-Comparator um:

Comparator<Student> byGroup =
    Comparator.comparing(Student::getGroup)
        .thenComparing(
            Comparator.comparingInt(Student::getPoints).reversed());

Das Ergebnis ist Ada, Chen, Eli, Dara, Ben: Gruppe A steht vor B; innerhalb von A steht 85 vor 64. Würde man stattdessen .reversed() ans Ende setzen, würden gegenüber der vorherigen Verkettung sowohl die Gruppen- als auch die Punktreihenfolge umgekehrt.

Regeln für Text und null wählen

Comparator.naturalOrder() folgt Comparable; reverseOrder() kehrt diese Ordnung um. Gib einen Schlüssel-Comparator an, wenn ein Schlüssel eine besondere Behandlung benötigt, wie in Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER).

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class SortNullable {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("b");
        words.add(null);
        words.add("a");
        words.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println(words); // [null, a, b]
    }
}

Umschließe den Vergleich auf der Ebene, auf der null vorkommt. nullsLast um den gesamten Student-Comparator behandelt null-Studentenobjekte. Eine Funktion zum Extrahieren des Namens, die null zurückgibt, benötigt einen Comparator mit Nullbehandlung für den Namensschlüssel selbst. Comparatoren für die natürliche Ordnung und gewöhnliche Schlüssel-Comparatoren behandeln null-Schlüssel nicht automatisch.

Map-Einträge sortieren und Typinferenz lösen

Eine HashMap sichert keine Begegnungsreihenfolge zu. Verarbeite ihre Einträge als Stream und wähle ausdrücklich eine Ordnung:

stock.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .forEach(System.out::println);

Für rice=12, oats=3 und flour=7 gibt dies rice=12, flour=7, oats=3 in getrennten Zeilen aus. Es ordnet die Einträge im Stream, ohne stock neu zu ordnen. Ergänze thenComparing(Map.Entry.comparingByKey()), wenn Werte mit Gleichstand eine vorhersehbare Schlüsselreihenfolge benötigen.

Das explizite <String, Integer> unterstützt die Typinferenz über den verketteten Aufruf hinweg. Ebenso kann Comparator.comparing(s -> s.getName()).reversed() für s den Typ Object ableiten. Verwende Student::getName oder (Student s) -> s.getName(), um den fehlenden Typ anzugeben. Ein benannter Comparator kann beim Sortieren von Listen, mit den Stream-Operationen sorted, min und max oder mit einer geordneten Map oder Menge wiederverwendet werden.

Wichtige Erkenntnisse

  • comparing extrahiert einen Schlüssel; Erzeugermethoden für primitive Typen vermeiden das Verpacken numerischer Schlüssel.
  • thenComparing wird nur verwendet, wenn der vorherige Vergleich einen Gleichstand ergibt.
  • reversed() kehrt den bereits erstellten Comparator um.
  • Behandle Nullwerte dort, wo sie auftreten: bei ganzen Objekten oder extrahierten Schlüsseln.
  • Das Sortieren eines Streams von Einträgen ordnet seine Quell-Map nicht neu.
4

Verarbeite Collections mit Streams

Eine Stream-Pipeline wandelt eine Collection in eine Folge von Verarbeitungsschritten um. Verfolge die Werte und ihre Typen, während ein Filter auswählt, eine Abbildung sie ändert und eine Terminaloperation das Ergebnis erstellt. Verzögerte Ausführung bedeutet, dass manche Elemente möglicherweise nie durchlaufen werden, während das Sortieren die gesamte Eingabe benötigt. Numerische Streams und optionale Werte machen Ergebnistypen deutlich, auch für den Fall, dass keine Werte übrig bleiben oder eine numerische Summe zu groß ist.

4.1 Baue eine Stream-Pipeline auf und verstehe ihren Ablauf

Ein Stream beschreibt, wie eine Folge von Werten verarbeitet wird. Er liest aus einer Quelle, leitet Elemente durch Zwischenoperationen und endet mit einer Terminaloperation, die ein Ergebnis liefert oder eine Aktion ausführt.

Die drei Teile erkennen
import java.util.List;

class CountPasses {
    public static void main(String[] args) {
        List<Integer> points = List.of(72, 45, 90, 58, 81);
        long passed = points.stream()
            .filter(p -> p >= 60)
            .count();
        System.out.println(passed); // 3
        System.out.println(points); // [72, 45, 90, 58, 81]
    }
}

points.stream() ist die Quelle. filter ist eine Zwischenoperation: Sie gibt einen weiteren Stream zurück. count ist die Terminaloperation: Sie gibt einen long zurück. Die Werte 72, 90 und 81 bestehen den Filter. Das Lesen über den Stream hat 45 und 58 nicht aus points entfernt.

Eine Quelle wählen

Collections stellen stream() bereit. Eine Map stellt Streams über entrySet(), keySet() oder values() bereit. Arrays.stream(array) verarbeitet Array-Elemente als Stream; ein int[] erzeugt einen IntStream. Stream.of("a", "b", "c") liefert aufgezählte Werte.

IntStream.range(1, 5) enthält 1, 2, 3, 4, wobei das Ende ausgeschlossen ist. rangeClosed(1, 5) schließt 5 ein und ergibt die Summe 15. Stream.iterate(1, n -> n * 2) hat keinen festen Endpunkt; .limit(5).toList() sammelt [1, 2, 4, 8, 16]. Ein begrenztes Ergebnis ist beim Verarbeiten dieser unendlichen Folge unerlässlich.

Die verzögerte Ausführung verfolgen

Zwischenoperationen werden verzögert ausgewertet. Das bloße Schreiben von names.stream().map(String::toUpperCase); führt die Umwandlungen nicht aus. Eine Terminaloperation startet die Verarbeitung, und Elemente werden nur bei Bedarf verarbeitet.

String first = java.util.stream.Stream.of("one", "two", "three", "four")
    .filter(w -> w.length() > 3)
    .map(String::toUpperCase)
    .findFirst()
    .orElse("none");
System.out.println(first); // THREE

Die ersten beiden Wörter bestehen den Filter nicht. "three" besteht ihn, wird zu "THREE" und erfüllt findFirst. Das letzte Wort wird nicht benötigt. Das ist Kurzschlussauswertung: Die Antwort steht fest, bevor die ganze Quelle gelesen wurde.

sorted benötigt die gesamte Eingabe, bevor es den ersten sortierten Wert ausgeben kann. Einige andere zustandsbehaftete Operationen, etwa ein sequenzielles distinct, können Werte ausgeben, sobald Eingaben eintreffen. Die Position einer Operation in der Verkettung beeinflusst, wie viel Arbeit nötig ist.

Für ein weiteres Ergebnis einen neuen Stream erstellen

Nach einer Terminaloperation ist der Stream verbraucht. Die erneute Verwendung dieses Streams wirft häufig eine IllegalStateException mit „stream has already been operated upon or closed“. Rufe für eine weitere Pipeline erneut points.stream() auf. Eine Collection ist wiederverwendbarer Speicher; ein Stream ist ein Verarbeitungspfad.

Das Erstellen eines Streams kopiert seine Elemente nicht. Halte die Quelle während der Verarbeitung unverändert. map erzeugt abgebildete Werte; es schreibt sie nicht in die Liste zurück. Für beabsichtigte Änderungen an einer Liste drücken Methoden wie replaceAll und removeIf diese Aufgabe direkt aus. Verwende eine Operation pro Zeile, damit Verarbeitungsreihenfolge und Ergebnistyp sichtbar bleiben.

Wichtige Erkenntnisse

  • Eine Pipeline hat eine Quelle, Zwischenoperationen und eine Terminaloperation.
  • Zwischenoperationen werden verzögert ausgewertet.
  • Kurzschlussauswertung kann das Lesen des Rests einer Quelle vermeiden.
  • sorted muss die gesamte Eingabe erfassen, bevor es sortierte Werte ausgibt.
  • Ein verbrauchter Stream muss durch einen neuen Stream aus der Quelle ersetzt werden.
  • Das Erstellen eines Streams kopiert seine Quelle nicht und schreibt sie auch nicht um.

4.2 Filtere Elemente, wandle sie um und ändere ihre Reihenfolge

Verfolge nach jeder Operation sowohl die Werte als auch ihren Typ: Ein späterer Lambda-Ausdruck erhält das aktuelle Stream-Element, nicht das ursprüngliche Quellobjekt.

Vor dem Umwandeln auswählen

filter behält Elemente, die ein Prädikat erfüllen. map ersetzt jedes Element durch einen berechneten Wert. Bei Studierenden mit Gettern für Namen und Punkte muss die Auswahl nach Punkten vor der Abbildung auf Namen erfolgen:

List<String> names = students.stream()
    .filter(s -> s.getPoints() >= 80)
    .map(Student::getName)
    .sorted()
    .toList();

Für Ada 85, Ben 72, Chen 85, Dara 90 und Eli 64 ergibt dies [Ada, Chen, Dara]. Vor map ist der Elementtyp Student. Danach ist der Typ String, sodass kein Punkte-Getter mehr verfügbar ist.

Zu einem Stream primitiver Werte wechseln

Stream<Integer> enthält Referenzen auf Wrapperobjekte und hat keine Methode sum(). mapToInt(Integer::intValue) erzeugt einen IntStream, der numerische Operationen bereitstellt. mapToInt(String::length) misst entsprechend Strings und liefert ihre Längen als primitive Werte.

import java.util.List;
import java.util.stream.IntStream;

class Windows {
    public static void main(String[] args) {
        List<Integer> window = IntStream.rangeClosed(1, 10)
            .skip(2)
            .limit(3)
            .boxed()
            .toList();
        System.out.println(window); // [3, 4, 5]
    }
}

skip(2) entfernt 1 und 2 aus dem Stream. limit(3) behält die nächsten drei Werte. Kehrt man diese Aufrufe um, bleibt nur 3 übrig. boxed() führt zurück zu Stream<Integer>, sodass die Elemente als Objekte gesammelt werden können. mapToObj erzeugt andere Objekte aus primitiven Werten; Streams von long- und double-Werten haben entsprechende Umwandlungsoperationen.

Duplikate entfernen und eine Ordnung wählen

distinct verwendet die Gleichheit von Objekten. Aus ["map", "filter", "map", "sorted", "filter"] wird [map, filter, sorted], und sorted() ergibt danach [filter, map, sorted]. Implementiere für eigene Klassen konsistente Methoden equals und hashCode, wenn übereinstimmende Feldwerte als Duplikate gelten sollen.

sorted() verwendet die natürliche Ordnung. Gib für eine andere Ordnung oder für Objekte ohne natürliche Ordnung einen Comparator an. Das Sortieren eines geordneten Streams ist stabil. Sortiert man die fünf Studierenden nach absteigender Punktzahl und nimmt dann zwei, werden Dara und Ada ausgewählt: Ada steht bei ihrer gleichen Punktzahl in der ursprünglichen Liste vor Chen. Ein Aufruf von limit(2) vor dem Sortieren würde nur die ursprünglichen ersten beiden Studierenden betrachten.

Verschachtelte Folgen abflachen
List<Integer> all = List.of(
        List.of(1, 2), List.of(3), List.of(4, 5, 6))
    .stream()
    .flatMap(List::stream)
    .toList();
System.out.println(all); // [1, 2, 3, 4, 5, 6]

Jede innere Liste wird zu einem Stream, und flatMap fügt ihre Elemente zu einem Stream zusammen. Ersetzt man hier flatMap durch map, ergibt sich ein Stream<Stream<Integer>>: ein Stream-Objekt pro innerer Liste, ohne Abflachung.

peek gibt einen Wert unverändert weiter und kann ihn zur Fehlersuche ausgeben. Seine Aktion wird nur für tatsächlich durchlaufene Elemente ausgeführt, und eine Implementierung kann eine nicht benötigte Stufe überspringen, zum Beispiel bei der Optimierung von count. Verwende es nicht, um notwendige Arbeit auszuführen. Verwende für einen einzelnen größten Wert max(comparator), statt jedes Element zu sortieren und eines zu entnehmen.

Wichtige Erkenntnisse

  • filter behält den Elementtyp bei; map kann ihn ändern.
  • Streams primitiver Werte bieten numerische Operationen, die Stream<Integer> nicht hat.
  • Die Reihenfolge von skip, limit und Sortierung ändert die ausgewählten Elemente.
  • distinct richtet sich nach der Gleichheit; das Sortieren geordneter Streams erhält bei Gleichständen die Reihenfolge.
  • flatMap flacht Streams ab, die aus den Eingabeelementen erzeugt werden.
  • peek dient der Untersuchung, nicht notwendigen Programmauswirkungen.

4.3 Reduziere einen Stream auf ein einzelnes Ergebnis

Wähle die Terminaloperation anhand des benötigten Ergebnisses: eine Anzahl, eine Zahl, ein Wahrheitswert, ein Element oder eine allgemeine Verknüpfung. Eine leere Eingabe ist immer dann relevant, wenn kein Ergebniswert ausgewählt werden kann.

Eine Zahl berechnen

count() gibt einen long zurück. sum() auf einem Stream primitiver Werte gibt eine Zahl des primitiven Typs des Streams zurück. Für die Punkte [72, 45, 90, 58, 81] ergibt das Filtern nach mindestens 60 und anschließende Summieren 243. Der Mittelwert dieser drei Werte ist 81,0.

import java.util.List;

class PointResults {
    public static void main(String[] args) {
        List<Integer> points = List.of(72, 45, 90, 58, 81);
        int sum = points.stream().filter(p -> p >= 60)
            .mapToInt(Integer::intValue).sum();
        double average = points.stream().filter(p -> p >= 60)
            .mapToInt(Integer::intValue).average().orElse(0.0);
        System.out.println(sum);     // 243
        System.out.println(average); // 81.0
    }
}

Die zweite Pipeline verwendet einen neuen Stream. Eine leere Summe ist 0, aber ein Mittelwert einer leeren Eingabe hat keinen Wert. average() gibt OptionalDouble zurück, und orElse(0.0) liefert einen gewählten Ersatzwert. Dieser Standardwert ist eine Entscheidung im Programm, nicht der mathematische Mittelwert einer leeren Menge.

Eine int-Summe kann überlaufen. Das Summieren zweier Werte von 2.000.000.000 ergibt als int den Wert -294.967.296. Wandle vor dem Addieren um: mapToInt(...).asLongStream().sum() ergibt 4.000.000.000 als long.

Einen Wert oder keinen behandeln

Ein Optional<T> enthält einen Wert oder keinen Wert. min, max, findFirst, findAny und eine Reduktion ohne neutrales Element geben bei Objekt-Streams optionale Werte zurück. points.stream().max(Integer::compare) ergibt Optional[90]; bei der Ausgabe des Containers erscheint auch der umgebende Text.

Verwende orElse für einen Ersatzwert, orElseGet für einen Ersatz-Supplier, ifPresent für eine Aktion oder map, um einen vorhandenen Wert umzuwandeln. Filtert man nach einem Studierenden mit mehr als 95 Punkten, bildet den ersten Treffer auf einen Namen ab und wendet orElse("nobody") an, ergibt sich für die Daten der fünf Studierenden "nobody". Der Aufruf von get() oder orElseThrow() auf einem leeren optionalen Wert wirft eine NoSuchElementException.

Eine Ja-oder-Nein-Frage stellen

anyMatch fragt, ob mindestens ein Wert die Bedingung erfüllt, allMatch, ob jeder Wert sie erfüllt, und noneMatch, ob keiner sie erfüllt. Sie können enden, sobald die Antwort feststeht. Bei leerer Eingabe ist anyMatch falsch, während allMatch und noneMatch wahr sind: Es gibt keinen Wert, der eine der beiden allgemeinen Aussagen verletzt.

Mit einem neutralen Element verknüpfen

Stream.of(3, 5, 7).reduce(0, Integer::sum) akkumuliert 0, 3, 8, 15. Für die Multiplikation beginne mit 1 und verwende (a, b) -> a * b, um 105 zu erhalten. Ein neutrales Element lässt einen Wert bei der Operation unverändert; 10 ist kein neutrales Element der Addition und würde die Summe fälschlich auf 25 verschieben. Der Akkumulator muss assoziativ sein, damit die Gruppierung das Ergebnis nicht ändert.

Ohne neutrales Element ergibt reduce(Integer::max) den Wert Optional[7] und bei leerer Eingabe einen leeren optionalen Wert. forEach führt hingegen eine Aktion aus und gibt void zurück; es eignet sich zur Ausgabe. Verwende eine Reduktion oder einen Collector, wenn die Aufgabe darin besteht, einen Ergebniswert zu erzeugen.

Wichtige Erkenntnisse

  • Anzahlen verwenden long; numerische Stream-Ergebnisse richten sich nach ihrem primitiven Typ.
  • Eine leere Summe ist null; ein Mittelwert oder Extremwert einer leeren Eingabe hat keinen Wert.
  • Verwende die Operationen eines optionalen Werts für den leeren Fall, bevor du seinen Wert ausliest.
  • Bei leerer Eingabe sind allMatch und noneMatch wahr; anyMatch ist falsch.
  • Eine Reduktion benötigt ein neutrales Element und einen assoziativen Akkumulator.
  • Wandle vor dem Summieren in long um, wenn die Summe der Werte den Wertebereich von int überschreitet.
5

Sammle Stream-Ergebnisse und verwende sie weiter

Ein nützliches Stream-Ergebnis kann eine Liste, Menge, Map, ein String oder eine Zusammenfassung für jede Gruppe sein. Der Collector bestimmt, was gespeichert wird und was bei wiederholten Schlüsseln geschieht. Gruppierung verbindet Auswahlregeln mit nachgeschalteten Zusammenfassungen; Partitionierung ergibt zwei boolesche Gruppen. In einer vollständigen Aufgabe kann dann die resultierende Map abgefragt werden. Der Vergleich dieser Pipeline mit einer Schleife hilft dir, lesbaren Code zu wählen und den Aufbau von Ergebnissen von Änderungen an externem Zustand zu trennen.

5.1 Sammle Ergebnisse in Listen, Mengen, Maps und Strings

Ein Collector wandelt einen Stream in eine nützliche Ergebnisstruktur um. Entscheide, was das Ergebnis unterstützen muss: Duplikate, eine Ordnung, spätere Änderungen oder doppelte Map-Schlüssel. Verschiedene Collectoren geben unterschiedliche Zusicherungen.

Eine Liste oder Menge wählen

stream.toList() gibt eine nicht modifizierbare Liste in Begegnungsreihenfolge zurück. Das Hinzufügen oder Entfernen von Elementen wirft eine UnsupportedOperationException. collect(Collectors.toList()) sammelt ebenfalls in einer Liste, garantiert aber keine bestimmte Implementierung oder Modifizierbarkeit. Fordere eine Liste, die du ändern willst, ausdrücklich an:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Gather {
    public static void main(String[] args) {
        List<String> words = Stream.of("a", "b", "a")
            .collect(Collectors.toCollection(ArrayList::new));
        words.add("c");
        System.out.println(words); // [a, b, a, c]
        System.out.println(Stream.of("a", "b", "a")
            .collect(Collectors.toSet()).size()); // 2
    }
}

toSet entfernt Duplikate und sichert keine Reihenfolge zu. toCollection(TreeSet::new) fordert eine sortierte Menge an. Eine Liste behält doppelte Elemente; wähle eine Menge, wenn das Ergebnis eindeutige Werte darstellt.

Text zusammenfügen

Collectors.joining(", ") fügt Strings mit einem Trennzeichen zusammen, ohne ein abschließendes Trennzeichen. Die Form mit drei Argumenten ergänzt außerdem ein Präfix und ein Suffix:

String names = Stream.of("Ada", "Chen", "Dara")
    .collect(Collectors.joining(", ", "[", "]"));
System.out.println(names); // [Ada, Chen, Dara]

Die Elemente müssen Werte vom Typ CharSequence sein, normalerweise Strings. Bilde Studierende mit Student::getName oder Zahlen mit String::valueOf ab, bevor du sie zusammenfügst. Das Zusammenfügen gibt einen String zurück, keine Liste, die nur ähnlich ausgegeben wird.

Map-Schlüssel und Werte wählen

Collectors.toMap(keyFunction, valueFunction) berechnet aus jedem Element einen Eintrag. Bei den fünf Studierenden Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64) ergeben sich beim Zuordnen der Namen zu Punkten fünf Einträge wie Ada=85 und Eli=64.

Bei Gruppenschlüsseln ist es anders: Mehrere Studierende haben dieselbe Gruppe. Ein toMap(Student::getGroup, Student::getPoints) mit zwei Argumenten wirft eine IllegalStateException, wenn ein Schlüssel erneut auftritt, selbst wenn beide Werte 85 sind. Eine Map benötigt einen Ergebniswert pro Schlüssel, und diese Form gibt keine Regel vor, um ihn auszuwählen.

Gib ein drittes Argument an, um doppelte Schlüssel zusammenzuführen:

Map<String, Integer> totals = students.stream()
    .collect(Collectors.toMap(
        Student::getGroup, Student::getPoints, Integer::sum));

Gruppe A erhält 85 + 85 + 64 = 234; Gruppe B erhält 72 + 90 = 162. Ersetzt du Integer::sum durch Math::max, bleiben die höchsten Punktzahlen pro Gruppe erhalten, A=85 und B=90. Verwende die Regel, die zur Aufgabe passt, und keinen beliebigen ersten Wert.

toMap sichert keine bestimmte Map-Reihenfolge zu. Ein viertes Argument kann eine solche anfordern, zum Beispiel TreeMap::new für sortierte Schlüssel. toMap(Student::getName, Student::getPoints, (a, b) -> a, TreeMap::new) behält bei doppelten Namen den ersten Wert und sortiert nach Namen. Ein Collector erspart auch das Erstellen einer externen Liste und deren Änderung in forEach; das Ergebnis wird von der Operation aufgebaut, die zum Sammeln vorgesehen ist.

Wichtige Erkenntnisse

  • Die von Stream.toList() zurückgegebene Liste ist nicht modifizierbar.
  • Collectors.toList() garantiert keine modifizierbare Implementierung.
  • Verwende toCollection, wenn ein bestimmter Collection-Typ benötigt wird.
  • joining fügt Text mit optionalem Trennzeichen, Präfix und Suffix zusammen.
  • toMap mit zwei Argumenten lehnt doppelte Schlüssel ab, auch wenn die doppelten Werte gleich sind.
  • Eine Zusammenführungsfunktion löst wiederholte Map-Schlüssel auf.
  • Fordere eine Map-Fabrik an, wenn deren Ordnung wichtig ist.

5.2 Gruppiere und partitioniere Daten mit Collectoren

Ein gruppiertes Ergebnis beantwortet für jeden Schlüssel eine Frage. Eine Partition beantwortet eine Ja-oder-Nein-Frage mit zwei Gruppen. Ein nachgeschalteter Collector bestimmt, was jede Gruppe speichert.

Nach einem berechneten Schlüssel gruppieren

groupingBy(classifier) erzeugt eine Map, die jedem vorkommenden Schlüssel eine Liste seiner Elemente zuordnet. Gruppiert man ["fig", "kiwi", "pear", "plum", "apple", "mango", "banana"] nach Wortlänge, erhält die Länge 4 die Liste [kiwi, pear, plum]. Für fehlende Längen gibt es keinen Eintrag; eine Abfrage gibt null zurück. Verwende daher getOrDefault(key, List.of()), wenn eine leere Liste nützlich ist.

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

class WordGroups {
    public static void main(String[] args) {
        List<String> words = List.of(
            "fig", "kiwi", "pear", "plum", "apple", "mango", "banana");
        Map<Integer, Long> counts = words.stream()
            .collect(Collectors.groupingBy(
                String::length, TreeMap::new, Collectors.counting()));
        System.out.println(counts); // {3=1, 4=3, 5=2, 6=1}
    }
}

Die Klassifikationsfunktion wählt ganzzahlige Schlüssel. TreeMap::new ordnet diese Schlüssel. counting() ersetzt die Liste jeder Gruppe durch ihre Größe als Long. Der Ergebnistyp ist daher Map<Integer, Long>, keine Map von Ganzzahlen auf Ganzzahlen.

Ändern, was eine Gruppe speichert

Für die Studierenden Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64) bietet das Gruppieren nach Student::getGroup diese Möglichkeiten:

Nachgeschalteter CollectorWert unter AWerttyp
counting()3Long
summingInt(Student::getPoints)234Integer
averagingInt(Student::getPoints)78.0Double
mapping(Student::getName, Collectors.toList())[Ada, Chen, Eli]List<String>

Ein nachgeschalteter Collector arbeitet innerhalb jeder Gruppe. Für A ist der Mittelwert 234 dividiert durch 3, also 78,0; der Mittelwert von B ist 81,0. maxBy und minBy speichern ein Optional, das den Extremwert der jeweiligen Gruppe enthält. Einfaches groupingBy sichert keinen bestimmten Map-Typ und keine Schlüsselreihenfolge zu; die Form mit Map-Fabrik ermöglicht eine ausdrückliche Wahl.

In zwei Antworten aufteilen
Map<Boolean, List<String>> partition = students.stream()
    .collect(Collectors.partitioningBy(
        s -> s.getPoints() >= 80,
        Collectors.mapping(Student::getName, Collectors.toList())));

Die Liste für true ist [Ada, Chen, Dara], die Liste für false ist [Ben, Eli]. Beide booleschen Schlüssel sind vorhanden, auch wenn eine Seite keine Studierenden enthält. Mit nachgeschaltetem counting() enthalten leere Seiten 0 als Long. groupingBy mit einer booleschen Klassifikationsfunktion erstellt dagegen nur Schlüssel, die vorkommen.

Die erstellte Map verarbeiten

Ein gruppiertes Ergebnis ist eine gewöhnliche Map. Durchlaufe sie mit forEach((group, value) -> ...) oder verarbeite ihre Einträge als Stream. Für die Mittelwerte A=78,0 und B=81,0 gibt averages.entrySet().stream().max(Map.Entry.comparingByValue()) ein Optional<Map.Entry<String, Double>> mit B=81.0 zurück. Der optionale Wert umschließt den Eintrag mit dem höchsten Wert; er ist nicht der einfache Wert 81,0. In einem separaten Schritt kann dieser Eintrag auf seinen Schlüssel oder Wert abgebildet und eine leere Map behandelt werden.

Wichtige Erkenntnisse

  • Eine Klassifikationsfunktion wählt Gruppenschlüssel; einfaches Gruppieren speichert Listen von Elementen.
  • Ein nachgeschalteter Collector ändert die Form des Ergebnisses jeder Gruppe.
  • counting liefert Long; Collectoren für Mittelwerte liefern Double.
  • Eine Partition enthält immer beide booleschen Schlüssel.
  • Ein fehlender Gruppenschlüssel gibt bei der Abfrage null zurück, keine leere Gruppe.

5.3 Wähle für eine vollständige Aufgabe zwischen Streams und Schleifen

Eine vollständige Datenaufgabe hat oft zwei Stufen: zuerst die Eingabe zusammenfassen, dann die Zusammenfassung abfragen. Wähle Streams, wenn sich diese Stufen natürlich als Standardoperationen lesen lassen, und Schleifen, wenn eine explizite Steuerung die Arbeit klarer macht.

Messwerte mitteln und dann Städte auswählen

Angenommen, ein Reading hat getCity() und getCelsius(). Die Messwerte in Grad Celsius sind Oslo 4,5, Lima 19,0, Oslo 6,0, Cairo 27,5 und Lima 21,0.

class Reading {
    private final String city;
    private final double celsius;
    Reading(String city, double celsius) {
        this.city = city;
        this.celsius = celsius;
    }
    String getCity() { return city; }
    double getCelsius() { return celsius; }
}

Erstelle die Eingabeliste vor jeder der beiden Implementierungen:

List<Reading> readings = List.of(
    new Reading("Oslo", 4.5), new Reading("Lima", 19.0),
    new Reading("Oslo", 6.0), new Reading("Cairo", 27.5),
    new Reading("Lima", 21.0));
Map<String, Double> averages = readings.stream()
    .collect(Collectors.groupingBy(
        Reading::getCity,
        TreeMap::new,
        Collectors.averagingDouble(Reading::getCelsius)));

List<String> warmCities = averages.entrySet().stream()
    .filter(e -> e.getValue() > 15)
    .map(Map.Entry::getKey)
    .toList();

Das erste Ergebnis ist {Cairo=27.5, Lima=20.0, Oslo=5.25}. Der Mittelwert von Oslo ist (4.5 + 6.0) / 2, also 5,25. Der von Lima ist 20,0. Das zweite Ergebnis ist [Cairo, Lima]; die TreeMap sorgt für eine aufsteigende Reihenfolge der Städte. Filtere die Mittelwerte nach dem Gruppieren: Filterst du zuerst einzelne Messwerte, beantwortest du eine andere Frage.

Für den höchsten einzelnen Messwert verwende readings.stream().max(Comparator.comparingDouble(Reading::getCelsius)). Das Ergebnis ist ein optionaler Wert mit dem Messwert aus Cairo, nicht definitionsgemäß die Stadt mit dem höchsten Mittelwert. Bei anderen Daten können diese Aufgaben unterschiedliche Antworten haben.

Dieselbe Berechnung mit Schleifen ausdrücken
Map<String, List<Double>> byCity = new TreeMap<>();
for (Reading r : readings) {
    byCity.computeIfAbsent(r.getCity(), k -> new ArrayList<>())
        .add(r.getCelsius());
}
Map<String, Double> averages = new TreeMap<>();
for (Map.Entry<String, List<Double>> e : byCity.entrySet()) {
    double sum = 0;
    for (double value : e.getValue()) sum += value;
    averages.put(e.getKey(), sum / e.getValue().size());
}
System.out.println(averages);
// {Cairo=27.5, Lima=20.0, Oslo=5.25}

Dies sind alternative Implementierungen desselben Ergebnisses; jede Gruppe ist nicht leer, weil sie durch einen Messwert erzeugt wurde. Der Stream macht das Gruppieren und Mitteln in einem Collector deutlich. Die Schleifen zeigen die Zwischenlisten und die Summe.

Das Verarbeitungsverhalten vorhersehbar halten

Ein Lambda-Ausdruck in einer Pipeline sollte aus seiner Eingabe rechnen, ohne externen Zustand zu ändern. Für die Punkte [72, 45, 90, 58, 81] erzeugt filter(p -> p >= 60).toList() direkt [72, 90, 81]. Fügt man innerhalb von forEach Werte zu einer externen Liste hinzu, verbirgt sich diese Aufgabe des Ergebnisaufbaus in einer Aktion.

Ändere eine ArrayList als Quelle nicht innerhalb ihrer Pipeline: Das Hinzufügen von Elementen aus einem forEach heraus kann eine ConcurrentModificationException werfen. Sammle neue Elemente zuerst separat und füge sie nach Abschluss der Verarbeitung hinzu. Eine eingefangene finale Referenz kann nach Javas Typregeln eine Zustandsänderung erlauben; das macht eine solche Änderung innerhalb eines Streams nicht geeignet.

Den klareren Kontrollfluss wählen

Verwende eine Schleife, wenn du benachbarte Indizes, eine komplizierte Abbruchbedingung oder mehrere aufeinander abgestimmte Aktualisierungen benötigst. Verwende Streams für eine gut lesbare Auswahl, Umwandlung und Sammlung. Die Stream-Syntax allein garantiert keine Geschwindigkeit. Benenne ein langes Prädikat oder einen langen Comparator und teile eine lange Verkettung an einem aussagekräftigen Zwischenergebnis auf. Untersuche zur Fehlersuche mit peek eine Stufe oder sammle sie in einer Liste und gib diese aus; fahre mit einem neuen Stream dieser Liste fort, statt eine verbrauchte Pipeline wiederzuverwenden.

Wichtige Erkenntnisse

  • Gruppiere vor dem Filtern, wenn die Bedingung Gruppenmittelwerte betrifft.
  • Ein größter einzelner Messwert und ein größter Gruppenmittelwert beantworten unterschiedliche Fragen.
  • Ein Collector drückt die Aufgabe des Ergebnisaufbaus direkt aus.
  • Halte das Verhalten der Pipeline frei von externen Aktualisierungen und Änderungen an der Quelle.
  • Verwende Schleifen, wenn Indizes oder eine abgestimmte Steuerung die Aufgabe klarer machen.
  • Untersuche eine als Liste gespeicherte Stufe und verwende für die Weiterarbeit einen neuen Stream.

Lambda-Ausdrücke und Streams in Java: Beispiele

5 Interaktive Lektionen

1

Erstelle eine typsichere, wiederverwendbare API

17 Schritte
Verfolge eine typisierte Box, eine generische Methode und eine Listenübertragung, um zu sehen, was Java vor der Ausführung prüfen kann.
  1. 1
    EinführungDie Typbeziehung erhalten

    Ein wiederverwendbarer Container muss sich merken, was der aufrufende Code hineinlegen und zurückerhalten darf. Ein Typparameter trägt diese Beziehung durch eine Deklaration.

    Du wirst eine Box erstellen, die Typwahl auf eine Methode verlagern und dann prüfen, was sich ändert, wenn ein Parameter eine Wildcard verwendet. Frage dich bei jedem Schritt, was der Compiler über einen Wert weiß, bevor das Programm läuft.

  2. 2
    ErklärungDie Box deklarieren

    T benennt innerhalb dieser Klasse den vom aufrufenden Code gewählten Typ:

    class Box<T> {
        private T value;
        Box(T value) { this.value = value; }
        T get() { return value; }
        void set(T value) { this.value = value; }
    }
    

    Das Feld, die Eingabe des Konstruktors, das Ergebnis des Getters und die Eingabe des Setters verwenden alle dasselbe T. Die Deklaration führt einen Platzhalter ein; ein bestimmter gespeicherter Typ wurde noch nicht gewählt.

  3. 3
    BeispielEinen konkreten Typ verwenden

    Erzeuge eine Box und lies ihren Wert:

    Box<String> b = new Box<>("tea");
    String drink = b.get();
    b.set("water");
    

    String gibt das Typargument an. Der Getter gibt einen String ohne explizite Typumwandlung zurück, und der Setter akzeptiert Strings. Der Diamantoperator rechts veranlasst Java, die Argumente abzuleiten. Er entfernt die Prüfungen nicht; ließe man die spitzen Klammern ganz weg, entstünde eine Verwendung als roher Typ.

  4. 4
    HerausforderungDen abgelehnten Schreibzugriff vorhersagen

    Die generische Box<T> hat void set(T value). Lässt sich nach Box<Integer> b = new Box<>(8); der Aufruf b.set("nine") kompilieren? Erkläre, wo der Fehler auftritt.

    ---reveal---

    Der Schreibzugriff schlägt fehl, weil diese Referenz einen Integer erwartet, keinen String. Ein späterer Lesezugriff ist nicht nötig, um den Fehler zu entdecken. Das Literal 8 kann als Integer verpackt werden; Typargumente selbst müssen weiterhin Referenztypen sein, daher ist Box<int> ungültig.

  5. 5
    VerbindungFür jeden Aufruf einen Typ wählen

    Eine Hilfsmethode kann ihren eigenen Typparameter einführen:

    static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }
    

    Das erste <T> deklariert den Parameter, und das nächste T benennt den Ergebnistyp. Ein Aufruf mit [4, 8, 15] gibt den Integer 15 zurück; ein Aufruf mit ["x", "y"] gibt den String "y" zurück. Die Methode benötigt unabhängig von ihren Typprüfungen eine nicht leere Liste.

  6. 6
    TippEinen Typzeugen nach dem Punkt schreiben

    Normalerweise leitet Java das Typargument der Methode aus der übergebenen Liste ab. Ein Aufruf kann es auch angeben:

    String word = Lists.<String>last(words);
    

    Der Typzeuge folgt auf den Punkt und steht vor last. Eine statische Methode innerhalb einer generischen Klasse benötigt weiterhin einen eigenen Parameter: static <E> Box<E> of(E value). Das T der Klasse gehört zu parametrisierten Instanzverwendungen, während die statische Methode zur gemeinsam genutzten Klasse gehört.

  7. 7
    HerausforderungEingabe und Ausgabe verknüpfen

    Eine Hilfsmethode soll ein Element aus einer List<E> zurückgeben, ohne dass der aufrufende Code eine explizite Typumwandlung schreiben muss. Jemand schreibt static E first(List<E> xs). Welche Deklaration fehlt?

    ---reveal---

    Ergänze <E> vor dem Rückgabetyp: static <E> E first(List<E> xs). Dies führt einen zur Methode gehörenden Parameter ein und verknüpft ihre Eingabeelemente mit ihrem Ergebnis. Object als Ergebnis zu schreiben würde diese nützliche Beziehung verlieren, statt den Fehler zu beheben.

  8. 8
    ErklärungVergleiche verfügbar machen

    Ein unbeschränktes T hat keine bekannte Operation compareTo. Eine Typschranke kann diese Zusicherung liefern:

    <T extends Comparable<T>>
    

    Eine Methode largest(List<T>) kann dann ein T best verwalten, jedes x mit best vergleichen und den Wert ersetzen, wenn x.compareTo(best) > 0 gilt. Für 3, 11 und 7 sind die jeweils besten Werte 3, dann 11, dann 11. Der Rückgabetyp bleibt der eigene Elementtyp der Liste.

  9. 9
    KernaussageEine Integer-Liste schützen

    Ein Integer ist ein Number, aber eine List<Integer> ist keine List<Number>.

    Wäre diese Listenzuweisung erlaubt, könnte Code über die Referenz auf die Zahlenliste ein Double hinzufügen. Der ursprüngliche Besitzer würde es dann als Integer lesen. Die Invarianz generischer Typen verhindert die Zuweisung, damit sowohl Lese- als auch Schreibzugriffe ihre Zusicherungen einhalten. ArrayList<Number> wird als List<Number> akzeptiert, weil das Element-Typargument weiterhin übereinstimmt.

  10. 10
    BeispielIn die sichere Richtung übertragen

    Eine extends-Wildcard erlaubt es dem Code, einen bekannten oberen Typ zu lesen; eine super-Wildcard erlaubt das Einfügen eines bekannten unteren Typs.

    static <T> void append(
            List<? super T> dst,
            List<? extends T> src) {
        dst.addAll(src);
    }
    

    Bei einer modifizierbaren List<Number> als Ziel und einer List<Integer> als Quelle kann Java für T den Typ Integer wählen. Jedes Quellelement ist für das Ziel akzeptabel, ohne zu behaupten, dass beide Listen identische Element-Typargumente haben.

  11. 11
    HerausforderungDen sicheren Lesezugriff wählen

    Ein Parameter hat den Typ List<? super Integer>. Kann sein erstes Element direkt einer Integer-Variablen zugewiesen werden? Welcher Typ ist stattdessen garantiert?

    ---reveal---

    Nur Object ist garantiert. Die tatsächliche Liste kann eine List<Number> oder List<Object> sein und bereits andere Werte als Ganzzahlen enthalten. Über diesen Typ kann sicher eine Ganzzahl hinzugefügt werden, aber die Schreiberlaubnis beschreibt nicht jedes bereits gespeicherte Element.

  12. 12
    ErklärungPrüfung und Laufzeitklassen trennen

    Die Typlöschung ersetzt Typparameter durch ihre Typschranken und verwendet Object, wenn keine Typschranke angegeben ist. Java hat die generischen Verwendungen bereits geprüft und fügt beim Lesen benötigte explizite Typumwandlungen ein.

    Eine Box<String> und eine Box<Integer> teilen sich dieselbe Laufzeitklasse Box. Es gibt keine separate Integer-Box-Klasse, die ein Laufzeittest erkennen könnte. Generics bewahren Typbeziehungen bei der Prüfung des Quellcodes. Deshalb entfernen sie viele sichtbare explizite Typumwandlungen, ohne die Klasse für jedes Argument zu spezialisieren.

  13. 13
    BeispielDas Erzeugungsverhalten bereitstellen

    Ein Typparameter kann nicht mit new T() instanziiert werden. Bitte stattdessen den aufrufenden Code um eine Fabrik:

    static <T> T create(Supplier<T> factory) {
        return factory.get();
    }
    List<String> xs = create(ArrayList::new);
    

    Die Referenz wählt über den Supplier-Zieltyp einen Listenkonstruktor. Generische Arrays wie new T[10] werden ebenfalls abgelehnt; eine Collection wie ArrayList<T> erfüllt meist die Speicheraufgabe.

  14. 14
    TippDie Warnung zum rohen Typ verfolgen

    Eine rohe List kann "text" und 42 aufnehmen und dabei Unchecked-Warnungen für die Aufrufe ausgeben. Eine spätere explizite Typumwandlung (String) schlägt fehl, wenn sie 42 erreicht.

    Ändert man die Deklaration zu List<String>, verlagert sich das Problem auf das ungültige Hinzufügen. Eine Unchecked-Warnung bedeutet, dass der Compiler eine Garantie verloren hat; für manche Daten kann die Ausführung trotzdem erfolgreich sein. Prüfe die Verwendung des rohen Typs oder die explizite Typumwandlung, die die Warnung verursacht hat, denn der letztlich fehlschlagende Lesezugriff kann weit entfernt sein.

  15. 15
    HerausforderungDie Überladung nach Typlöschung prüfen

    Eine Klasse deklariert show(Set<String> xs) und show(Set<Integer> xs). Warum kann Java die Element-Typargumente nicht verwenden, um eine Überladung auszuwählen?

    ---reveal---

    Beide Parametertypen werden durch die Typlöschung zu Set, sodass die Deklarationen dieselbe Signatur nach Typlöschung haben. Gib ihnen unterschiedliche Namen oder eine gemeinsame Operation. Ein Laufzeittest von Object auf List<String> hat eine verwandte Einschränkung: Prüfe auf List<?>, ohne dessen Element-Typargument zu behaupten.

  16. 16
    HerausforderungEine wiederverwendbare Übertragungs-API prüfen

    Eine API verwendet eine Quellliste mit rohem Typ, wandelt jedes Element explizit in Number um und hängt es an eine Zahlenliste an. Sie soll Ganzzahlen und Double-Werte als Quellen akzeptieren. Welcher Quellparameter entfernt diese Typumwandlung und erhält zugleich sichere numerische Lesezugriffe?

    ---reveal---

    Verwende List<? extends Number>. Jedes Element kann als Number gelesen werden, und sowohl Integer- als auch Double-Listen passen. List<Number> würde diese engeren Listenargumente ablehnen. Das Ziel sollte modifizierbar sein; sein generischer Typ allein kann erfolgreiche Schreibzugriffe nicht garantieren.

  17. 17
    ZusammenfassungEine API anhand ihrer Zusicherungen lesen

    Bestimme beim Prüfen einer generischen Deklaration, wo jeder Typparameter eingeführt wird, welche Positionen er verbindet und welche Operationen seine Typschranke bereitstellt.

    Markiere bei einer Übertragung die Quelle als Erzeuger und das Ziel als Verbraucher. Suche bei einem Erzeugungsschritt nach übergebenem Konstruktionsverhalten. Frage dich bei einer Warnung oder abgelehnten Operation, ob generische Informationen umgangen wurden oder ob die Operation zur Laufzeit einen Elementtyp benötigen würde.

2

Verhalten zu einem Argument machen

14 Schritte
Erstelle und kombiniere funktionale Regeln und lies anschließend eingefangene Variablen und Methodenreferenzen korrekt.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungMit der Form des Verhaltens beginnen
  2. 2
    ErklärungDie zu implementierende Methode finden
  3. 3
    BeispielBei Bedarf einen Block für eine Entscheidung verwenden
  4. 4
    HerausforderungDas fehlende Ergebnis ergänzen
  5. 5
    ErklärungVier häufige Aufgaben zuordnen
  6. 6
    BeispielAuf die Reihenfolge der Komposition achten
  7. 7
    VerbindungCollections die Regel ausführen lassen
  8. 8
    HerausforderungEntfernen und Auswählen unterscheiden
  9. 9
    ErklärungEine Variable ohne Neuzuweisung einfangen
  10. 10
    BeispielDas Empfängerobjekt aus der Referenz bestimmen
  11. 11
    HerausforderungPrüfen, was final schützt
  12. 12
    BeispielMit einer Aktualisierungsregel für Maps zählen
  13. 13
    HerausforderungZieltypisierung und Einfangen verbinden
  14. 14
    ZusammenfassungEinen Aufruf durch die Regel verfolgen
3

Eine Ordnung Schritt für Schritt aufbauen

12 Schritte
Verfolge Vergleichsvorzeichen, Gleichstände und Umkehrungen, bevor du einen Comparator auf Listen oder Map-Einträge anwendest.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungDie benötigte Ordnung benennen
  2. 2
    ErklärungEin Vergleichsergebnis lesen
  3. 3
    BeispielSubtraktion bei numerischen Vergleichen vermeiden
  4. 4
    BeispielGleiche Punktzahlen in Quellreihenfolge belassen
  5. 5
    HerausforderungNatürliche und aufgabenspezifische Ordnung trennen
  6. 6
    ErklärungEine Regel nur für Gleichstände ergänzen
  7. 7
    KernaussageNur die beabsichtigten Entscheidungen umkehren
  8. 8
    TippNullwerte auf der passenden Ebene behandeln
  9. 9
    HerausforderungDie vollständige Umkehrung lesen
  10. 10
    VerbindungDie Einträge ordnen, nicht die Map
  11. 11
    HerausforderungMap-Einträge mit Gleichstand vorhersehbar ordnen
  12. 12
    ZusammenfassungDen Vergleichsbaum prüfen
4

Werte durch einen Stream verfolgen

16 Schritte
Verfolge Werte und Typen einer Pipeline, wähle dann ein Terminalergebnis und behandle fehlende Werte.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungWerte und Typen gemeinsam verfolgen
  2. 2
    ErklärungDas Terminalergebnis finden
  3. 3
    BeispielNach dem ersten Treffer anhalten
  4. 4
    HerausforderungEine weitere Berechnung ausführen
  5. 5
    BeispielStudierende vor dem Abbilden auf Namen auswählen
  6. 6
    VerbindungZu numerischen Operationen wechseln
  7. 7
    BeispielVor dem Entfernen von Duplikaten abflachen
  8. 8
    HerausforderungDie Reihenfolge der Operationen lesen
  9. 9
    BeispielEine Summe und einen Mittelwert berechnen
  10. 10
    ErklärungEinen fehlenden von einem vorhandenen Wert unterscheiden
  11. 11
    HerausforderungEin fehlendes Maximum behandeln
  12. 12
    KernaussageAllgemeine Prüfungen bei leerer Eingabe prüfen
  13. 13
    BeispielEinen neutralen Startwert wählen
  14. 14
    TippVor dem Überlauf den Wertebereich erweitern
  15. 15
    HerausforderungAbflachen, Auswahl und ein Ergebnis verbinden
  16. 16
    ZusammenfassungDie Randfälle des Ergebnisses prüfen
5

Eine nützliche Datenzusammenfassung erstellen

15 Schritte
Wähle Ergebniscontainer, löse doppelte Schlüssel auf und frage Zusammenfassungen pro Gruppe ohne versteckte Aktualisierungen ab.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungVom gewünschten Ergebnis rückwärts arbeiten
  2. 2
    ErklärungSpätere Änderungen ausdrücklich verlangen
  3. 3
    BeispielKollisionen mit einer Zusammenführungsregel lösen
  4. 4
    HerausforderungEinen Wert pro wiederholtem Schlüssel wählen
  5. 5
    VerbindungWerte als Text zusammenfügen
  6. 6
    ErklärungEinen Schlüssel und dann ein Gruppenergebnis wählen
  7. 7
    BeispielInnerhalb der Studierendengruppen zusammenfassen
  8. 8
    KernaussageBeide Seiten einer Partition erhalten
  9. 9
    HerausforderungEine leere Seite vorhersagen
  10. 10
    BeispielZuerst Mittelwerte der Städte berechnen
  11. 11
    BeispielDie erstellte Zusammenfassung abfragen
  12. 12
    TippDen Kontrollfluss bei Bedarf sichtbar machen
  13. 13
    HerausforderungDie Quelle während der Verarbeitung schützen
  14. 14
    HerausforderungDie Gruppe mit dem höchsten Wert sicher finden
  15. 15
    ZusammenfassungDie gesamte Datenaufgabe überprüfen

Lambda-Ausdrücke und Streams in Java: Übungsaufgaben

50 Übungsfragen in 5 Sets

Set 1: Wende die Grundlagen an10 Übungsfragen

Frage 1 von 10

Eine generische Box<T> hat T get() und void set(T value). Was ist bei Box<String> label = new Box<>("north"); der deklarierte Ergebnistyp von label.get()?
  1. Object
  2. String
  3. Box<String>
  4. CharSequence
Antwort zeigenAntwort ausblenden
Antwort: B
Das Typargument String ersetzt den Klassenparameter T im deklarierten Ergebnis des Getters. Der Compiler behandelt den Aufruf daher so, dass er einen String zurückgibt, ohne dass der aufrufende Code eine explizite Typumwandlung schreiben muss. Die Typlöschung macht das Ergebnis auf Quellcodeebene nicht zu Object; die Prüfung zur Kompilierzeit verwendet weiterhin das Argument.

Frage 2 von 10

Eine gewöhnliche Hilfsklasse benötigt eine statische Methode, die das erste Element einer beliebigen nicht leeren Liste zurückgibt und dessen Elementtyp erhält. Welche Deklaration passt?
  1. static E first(List<E> xs)
  2. static <E> Object first(List<Object> xs)
  3. static <E> List<E> first(E xs)
  4. static <E> E first(List<E> xs)
Antwort zeigenAntwort ausblenden
Antwort: D
Die Methode führt E vor ihrem Rückgabetyp ein und wiederholt es für die Eingabeelemente und das Ergebnis. Dadurch erhält jeder Aufruf ein eigenes abgeleitetes Typargument. Das bloße Schreiben von E an den Parameter- und Rückgabepositionen deklariert es nicht, daher schlägt die Version ohne <E> in einer gewöhnlichen Hilfsklasse fehl.

Frage 3 von 10

Eine Methode liest eine Liste von Zahlen nur, um auf jedem Element doubleValue() aufzurufen. Sie muss sowohl List<Integer> als auch List<Double> akzeptieren. Welcher Parametertyp passt?
  1. List<? extends Number>
  2. List<Number>
  3. List<? super Number>
  4. List<Object>
Antwort zeigenAntwort ausblenden
Antwort: A
Eine extends-Wildcard akzeptiert einen unbekannten Untertyp von Number, sodass jeder gelesene Wert als Number behandelt werden kann. List<Number> kann keine List<Integer> akzeptieren, weil generische Typen invariant sind. Eine super-Wildcard würde allgemeinere Elementtypen erlauben, aber gelesene Werte hätten nur den Typ Object.

Frage 4 von 10

Eine Function<Integer, Integer> soll ihre Eingabe mit einem Blockrumpf verdoppeln. Welcher Lambda-Ausdruck passt?
  1. x -> { x * 2; }
  2. x -> { return; }
  3. x -> { return x * 2; }
  4. () -> { return x * 2; }
Antwort zeigenAntwort ausblenden
Antwort: C
Ein Block, der einen Wert zurückgibt, muss diesen ausdrücklich zurückgeben. return x * 2; liefert das von der Funktion verlangte ganzzahlige Ergebnis. Ein Lambda-Ausdruck mit Ausdrucksrumpf könnte einfach x -> x * 2 verwenden, aber beim Hinzufügen geschweifter Klammern bleibt diese automatische Rückgabe nicht erhalten.

Frage 5 von 10

Ein Predicate soll jeden geprüften String protokollieren und den anfänglichen Schwellenwert verwenden, während sich ein separater Zähler namens limit weiter ändern darf. Der Code fängt beide lokalen Variablen ein: int limit = 4; List<String> notes = new ArrayList<>(); Predicate<String> p = s -> { notes.add(s); return s.length() > limit; }; limit++;. Welche Korrektur erfüllt diese Anforderungen weiterhin?
  1. Die Listenreferenz final machen und das veränderliche limit weiter einfangen.
  2. Das Inkrement vor den Lambda-Ausdruck setzen und dasselbe limit einfangen.
  3. Einen separaten festen Schwellenwert einfangen und dieselbe Listenreferenz behalten.
  4. Die Liste durch eine nicht modifizierbare Liste ersetzen und limit weiter einfangen.
Antwort zeigenAntwort ausblenden
Antwort: C
Das Inkrement verhindert das Einfangen von limit. Kopiere daher seinen Anfangswert in eine separate effektiv finale lokale Variable und fange diesen Schwellenwert ein. Die unveränderte Referenz notes darf weiterhin auf eine Liste verweisen, die Protokolleinträge erhält. Eine Verlagerung des Inkrements nach vorn stellt die effektive Finalität nach einer Neuzuweisung nicht wieder her.

Frage 6 von 10

Was besagt bei aufsteigender Ordnung ein negatives Ergebnis von compare(a, b)?
  1. a gehört vor b.
  2. a gehört nach b.
  3. a und b sind gleiche Objekte.
  4. a liegt genau eine Position niedriger.
Antwort zeigenAntwort ausblenden
Antwort: A
Das Vorzeichen des Vergleichs bestimmt die relative Position: Ein negatives Ergebnis setzt das erste Argument vor das zweite. Null bezeichnet einen Gleichstand in der Ordnung, während ein positives Ergebnis das erste Argument nach das zweite setzt. Der Betrag des negativen Ergebnisses stellt weder einen Abstand noch eine Anzahl von Positionen dar.

Frage 7 von 10

Ein Programm erstellt names.stream().filter(s -> s.length() > 2) und macht nichts mit dem zurückgegebenen Stream. Wann werden die Predicate-Aufrufe ausgeführt?
  1. Alle werden beim Aufruf von stream() ausgeführt.
  2. Alle werden beim Aufruf von filter() ausgeführt.
  3. Sie werden beim nächsten Lesen der Quellliste ausgeführt.
  4. Ohne Terminaloperation werden sie nicht ausgeführt.
Antwort zeigenAntwort ausblenden
Antwort: D
filter ist eine Zwischenoperation und legt eine verzögert ausgewertete Verarbeitungsstufe fest. Hier startet keine Terminaloperation den Durchlauf, daher wird das Predicate nicht aufgerufen. Das Erstellen eines Streams kopiert oder filtert die Quellliste nicht sofort, und ein späteres unabhängiges Lesen der Liste führt diese Pipeline nicht aus.

Frage 8 von 10

Ein nicht leerer Stream enthält 2, 4 und 6. Ein Produkt wird mit reduce(0, (a, b) -> a * b) berechnet. Was verursacht das falsche Ergebnis?
  1. Der Akkumulator muss ein statt zwei Argumente entgegennehmen.
  2. Null ist nicht das neutrale Element der Multiplikation und hält den Akkumulator bei null.
  3. Ein Stream von Ganzzahlen kann nicht mit Multiplikation reduziert werden.
  4. Der erste Eingabewert muss immer als explizites neutrales Element dienen.
Antwort zeigenAntwort ausblenden
Antwort: B
Die Multiplikation der anfänglichen null mit 2 ergibt null, und jede weitere Multiplikation bleibt bei null. Verwende 1 als neutrales Element, damit der Akkumulator den ersten Wert erhält und anschließend das Produkt 48 aufbaut. Ein neutrales Element wird für die Operation gewählt und nicht beliebig aus den Eingabewerten genommen.

Frage 9 von 10

Eine Ergebnisliste muss ein späteres add unterstützen. Welcher Terminalausdruck stellt ausdrücklich eine ArrayList bereit?
  1. toList()
  2. collect(Collectors.toList())
  3. collect(Collectors.toCollection(ArrayList::new))
  4. collect(Collectors.toSet())
Antwort zeigenAntwort ausblenden
Antwort: C
toCollection(ArrayList::new) fordert eine bekannte modifizierbare Listenimplementierung an. Stream.toList() ist nicht modifizierbar, und Collectors.toList() garantiert keine Modifizierbarkeit, selbst wenn eine bestimmte Implementierung zufällig Änderungen erlaubt. Ein Mengen-Collector ändert außerdem die Struktur des Ergebnisses und den Umgang mit Duplikaten.

Frage 10 von 10

Eine Pipeline auf einer ArrayList<String> words ruft forEach(w -> words.add(w + "!")) auf. Wie sollte das Programm sicher eine umgewandelte Kopie jedes ursprünglichen Wortes anhängen?
  1. Die Kopien separat sammeln und dann words.addAll(copies) aufrufen.
  2. peek(w -> words.add(w + "!")) vor forEach ergänzen.
  3. forEach durch map ersetzen und words.add darin belassen.
  4. words in einer anderen Variablen speichern und über diese Referenz hinzufügen.
Antwort zeigenAntwort ausblenden
Antwort: A
Wenn die Kopien zuerst gesammelt werden, kann der Durchlauf enden, bevor die ursprüngliche Liste geändert wird. Das Hinzufügen zu einer ArrayList, während ihr Stream sie liest, beeinträchtigt den Durchlauf und kann eine ConcurrentModificationException werfen. Eine andere Referenz zeigt weiterhin auf dieselbe Quelle; sie trennt die Lese- und Schreibstufen nicht.
Set 2: Verfolge Typen und Ergebnisse10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Eine Box<Integer> enthält 12. Das Programm weist int next = box.get() + 3; zu. Wie kann das kompiliert werden und 15 ergeben?
  1. Der Getter gibt int zurück, weil generische Argumente primitiv sind.
  2. Der Getter gibt Object zurück, das beim Rechnen automatisch umgewandelt wird.
  3. Der Getter gibt Integer zurück, das beim Rechnen entpackt wird.
  4. Der Getter gibt Double zurück, das beim Rechnen automatisch verengt wird.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine statische Methode pick gehört zur Klasse Items und deklariert <T> T pick(List<T> xs). Wie wird bei einem Aufruf ein explizites Typargument String geschrieben?
  1. Items.<String>pick(words)
  2. Items.pick<String>(words)
  3. <String>Items.pick(words)
  4. Items.String.pick(words)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Was ersetzt T bei der Typlöschung innerhalb einer unbeschränkten generischen Methode?
  1. String
  2. Object
  3. Class<T>
  4. Number
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Ein Interface deklariert int run(int x), eine Default-Methode reset() und eine statische Methode help(). Kann x -> x + 2 es implementieren?
  1. Nein; drei deklarierte Methoden benötigen drei Lambda-Rümpfe.
  2. Ja; ein Lambda-Ausdruck liefert auch Default- und statische Rümpfe.
  3. Nein; eine funktionale Methode muss apply heißen.
  4. Ja; nur run benötigt eine Implementierung der abstrakten Methode.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine wiederverwendbare Regel nimmt einen String entgegen und entscheidet, ob er mit ".csv" endet. Welches Standardinterface passt zur von filter erwarteten Bedingung?
  1. Predicate<String>
  2. Consumer<String>
  3. Supplier<Boolean>
  4. Function<String, Boolean>
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Die Elemente sind Mira (Gruppe A, 7), Niko (B, 9), Oren (A, 4), Pia (B, 6). Sie werden mit comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed() sortiert. Welche Namensreihenfolge ergibt sich?
  1. [Pia, Niko, Oren, Mira]
  2. [Niko, Pia, Mira, Oren]
  3. [Mira, Oren, Niko, Pia]
  4. [Oren, Mira, Pia, Niko]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Welche Liste ergibt sich aus IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList()?
  1. [2, 3]
  2. [4, 5]
  3. [5, 6]
  4. [7, 8]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Welche Ergebnisse liefern für Stream.<Integer>empty() die getrennten Aufrufe auf jeweils neuen Streams anyMatch(n -> n > 0), allMatch(n -> n > 0) und noneMatch(n -> n > 0) in dieser Reihenfolge?
  1. false, false, true
  2. true, true, false
  3. false, false, false
  4. false, true, true
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein toMap(Item::getCode, Item::getCount) mit zwei Argumenten trifft auf zwei Elemente mit dem Code "R", beide mit der Anzahl 3. Was geschieht?
  1. Es speichert R=3, weil die wiederholten Werte gleich sind.
  2. Es speichert R=6, indem es gleiche Werte automatisch addiert.
  3. Es speichert beide Einträge unter zwei Kopien des Schlüssels R.
  4. Es wirft für den wiederholten Schlüssel eine IllegalStateException.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein leerer Stream wird mit partitioningBy(x -> x > 10, Collectors.counting()) gesammelt. Welches Ergebnis ist garantiert?
  1. Die Map ist leer, weil keine Klassifikationsfunktion ausgeführt wurde.
  2. Beide booleschen Schlüssel werden auf 0L abgebildet.
  3. Nur false ist vorhanden und wird auf 0L abgebildet.
  4. Beide booleschen Schlüssel werden auf leere optionale Werte abgebildet.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 3: Wähle eine sichere Implementierung10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Ein generisches Pair<L, R> hat Getter, die L und R zurückgeben. Welchen Typ hat bei Pair<String, Integer> p = new Pair<>("zone", 4); der Ausdruck p.getFirst() und welchen Wert hat p.getSecond() + 2?
  1. String und 6
  2. Object und 6
  3. String und 42
  4. Integer und 6
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein Parameter ist als List<? super Integer> sink deklariert. Welcher spezifischste Typ ist für sink.get(0) garantiert?
  1. Integer
  2. Number
  3. Object
  4. Comparable<Integer>
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine Klasse deklariert sowohl show(Set<Long> xs) als auch show(Set<String> xs). Warum schlägt die Kompilierung fehl?
  1. Jede generische Methode muss ihren Elementtyp zurückgeben.
  2. Set erlaubt nur ein Typargument in einer ganzen Klasse.
  3. Java-Überladungen können überhaupt keine Collection-Parameter verwenden.
  4. Beide Parametertypen werden durch Typlöschung zu Set.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Eine Methode soll für jeden String eine übergebene Aktion ausführen, ohne von dieser Aktion ein Ergebnis zu erhalten. Welches Parameterinterface passt?
  1. Supplier<String>
  2. Consumer<String>
  3. Predicate<String>
  4. UnaryOperator<String>
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Klasse Badge hat einen Konstruktor Badge(String text). Eine Methode akzeptiert Function<String, Badge> factory. Welche Referenz erzeugt dasselbe Verhalten wie s -> new Badge(s)?
  1. Badge::toString
  2. Badge::new
  3. new Badge::new
  4. Badge::Badge
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Eine Liste ist ["z", null, "a"]. Sie wird mit Comparator.nullsLast(Comparator.reverseOrder()) sortiert. Welche Reihenfolge ergibt sich?
  1. [null, z, a]
  2. [a, z, null]
  3. [null, a, z]
  4. [z, a, null]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine Stream-Variable s wird für s.count() und danach für s.toList() verwendet. Wie sollte das Programm beide Ergebnisse aus einer wiederverwendbaren Liste data erhalten?
  1. Für jede Terminaloperation einen neuen data.stream() starten.
  2. Zwischen den Terminaloperationen s.close() aufrufen.
  3. s vor dem Aufruf von count() in einer anderen Variablen speichern.
  4. Nach dem Aufruf von count() ein s.limit(data.size()) einfügen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Welche Liste ergibt sich aus Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList()?
  1. [3, 4]
  2. [3, 3, 4, 4]
  3. [3, 3, 4]
  4. [4, 3, 3]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Elemente haben die Codes und Mengen P=2, Q=6, P=5, Q=1. Das Programm soll eine modifizierbare Liste unterschiedlicher Codes in aufsteigender Ordnung zurückgeben. Welche Pipeline passt?
  1. items.stream().map(Item::getCode).distinct().sorted().toList()
  2. items.stream().map(Item::getCode).distinct().sorted(Comparator.reverseOrder()).collect(Collectors.toCollection(ArrayList::new))
  3. items.stream().map(Item::getCode).distinct().sorted().collect(Collectors.toCollection(ArrayList::new))
  4. items.stream().map(Item::getCode).sorted().collect(Collectors.toCollection(ArrayList::new))
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine Aufgabe vergleicht jedes Listenelement mit dem unmittelbar vorherigen und aktualisiert unter mehreren Bedingungen zwei Zähler. Was macht eine indexbasierte Schleife zu einer sinnvollen Wahl?
  1. Sie zeigt benachbarte Indizes und abgestimmte Zähleraktualisierungen.
  2. Sie ist nötig, weil Streams keine benachbarten Listenpositionen prüfen können.
  3. Sie garantiert eine schnellere Verarbeitung als ein Stream über dieselbe Liste.
  4. Sie erlaubt das Hinzufügen zur Quelle während eines gewöhnlichen for-each-Durchlaufs.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 4: Verfolge die vollständige Aufgabe10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Eine generische Klasse Slot<T> benötigt eine statische Fabrik, die aus einem beliebigen Wert einen typisierten Slot erzeugt. Welche Deklaration vermeidet die Verwendung des Klassenparameters aus einem statischen Kontext?
  1. static Slot<T> of(T value)
  2. static <E> Slot<T> of(E value)
  3. static Slot<Object> of(T value)
  4. static <E> Slot<E> of(E value)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine Methode überträgt mit addAll einen beliebig gewählten Elementtyp aus einer Quellliste in eine modifizierbare Zielliste. Sie soll Ganzzahlen entweder in eine Zahlenliste oder in eine Objektliste kopieren und dabei typsicher bleiben. Welche Signatur passt?
  1. <T> void append(List<? extends T> dst, List<? super T> src)
  2. <T> void append(List<T> dst, List<T> src)
  3. <T> void append(List<? super T> dst, List<? extends T> src)
  4. <T> void append(List<Object> dst, List<T> src)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Ein Interface deklariert boolean check(String s) und boolean equals(Object other) und hat keine weiteren abstrakten Methoden. Kann ein Lambda-Ausdruck es implementieren, und warum?
  1. Ja; die Übereinstimmung mit einer Object-Methode fügt keine abstrakte Implementierungsarbeit hinzu.
  2. Nein; jede deklarierte abstrakte Methode zählt, daher hat das Interface zwei.
  3. Ja; Java stellt bei jedem Interface mit zwei Methoden immer eine fehlende Methode bereit.
  4. Nein; eine funktionale Methode mit booleschem Rückgabewert muss test heißen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Welcher Lambda-Ausdruck hat dasselbe Verhalten wie die ungebundene Referenz String::compareToIgnoreCase, wenn sie als Comparator<String> verwendet wird?
  1. (a, b) -> b.compareToIgnoreCase(a)
  2. (a, b) -> a.compareToIgnoreCase(b)
  3. a -> a.compareToIgnoreCase(a)
  4. (a, b) -> String.compareToIgnoreCase(a, b)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine modifizierbare Liste enthält in dieser Reihenfolge X mit 8 Punkten, Y mit 3 Punkten, Z mit 8 Punkten und W mit 3 Punkten. Sie wird mit einem gültigen Comparator nur nach aufsteigender Punktzahl sortiert. Welche Namensreihenfolge ist garantiert?
  1. [Y, W, X, Z]
  2. [W, Y, Z, X]
  3. [Y, W, Z, X]
  4. [X, Z, Y, W]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Comparator für Messwerte vom Typ double verwendet (int) (a - b). Was geschieht beim Vergleich von 3,6 mit 3,1?
  1. Er meldet korrekt, dass der erste Wert größer ist.
  2. Er meldet korrekt, dass der erste Wert kleiner ist.
  3. Er meldet einen Gleichstand, obwohl der erste Wert größer ist.
  4. Er wirft bei einem gebrochenen Ergebnis eine Ausnahme.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Was ist der Ergebnistyp von List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream)?
  1. Stream<String>
  2. Stream<Stream<String>>
  3. Stream<List<String>>
  4. List<Stream<String>>
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Eine Methode gibt die größte Ganzzahl in einer beliebigen Liste zurück oder -1, wenn die Liste leer ist. Welcher Ausdruck erfüllt diese Anforderung?
  1. xs.stream().reduce(-1, Integer::max)
  2. xs.stream().max(Integer::compare).get()
  3. xs.stream().findFirst().orElse(-1)
  4. xs.stream().max(Integer::compare).orElse(-1)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Die Wörter sind "reed", "oak", "fern", "elm", "cedar". Welche Map wird nach dem Gruppieren nach Länge mit counting() in einer TreeMap ausgegeben?
  1. {3=1, 4=2, 5=2}
  2. {3=2, 4=1, 5=1}
  3. {3=2, 4=2, 5=1}
  4. {2=3, 1=5}
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Die Messwerte sind Delta 6 °C, Delta 18 °C, Cove 14 °C und Cove 16 °C. Gesucht sind Städte, deren Mittelwert 13 °C überschreitet. Welches Ergebnis ist richtig?
  1. Nur Cove erfüllt die Bedingung.
  2. Cove und Delta erfüllen die Bedingung.
  3. Nur Delta erfüllt die Bedingung.
  4. Keine Stadt erfüllt die Bedingung.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 5: Übertrage die Ideen auf neue Aufgaben10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Eine Methode hat die Parameter List<? super Integer> dst, List<? extends Number> src und versucht dst.add(src.get(0)). Sie muss Quellen mit Integer- oder Double-Werten und modifizierbare Ziele mit Number- oder Object-Werten akzeptieren. Wie sollten ihre Parameter geändert werden?
  1. List<? super Integer> dst, List<? extends Integer> src
  2. List<? super Number> dst, List<? extends Number> src
  3. List<Number> dst, List<? extends Number> src
  4. List<? extends Number> dst, List<? super Number> src
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine generische Fabrik soll einen neuen Wert des vom aufrufenden Code gewählten Referenztyps erzeugen, ohne vorauszusetzen, dass ein parameterloser Konstruktor existiert. Welcher Entwurf passt?
  1. new T() innerhalb einer Methode verwenden, die <T> deklariert.
  2. new Object() verwenden und es direkt als T zurückgeben.
  3. Einen Supplier<T> entgegennehmen und dessen Methode get() aufrufen.
  4. T.class verwenden, um den Konstruktor des aufrufenden Codes zu finden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Was gibt triple.andThen(shift).apply(4) für Function<Integer, Integer> triple = x -> x * 3; und shift = x -> x + 2; zurück?
  1. 18
  2. 12
  3. 20
  4. 14
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Eine HashMap<String, Integer> enthält oak=6, ash=6, elm=2. Welcher Comparator für Einträge garantiert absteigende Werte und bei Gleichständen aufsteigende Schlüssel?
  1. Map.Entry.<String, Integer>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey())
  2. Map.Entry.<String, Integer>comparingByValue().thenComparing(Map.Entry.comparingByKey()).reversed()
  3. Map.Entry.<String, Integer>comparingByKey().thenComparing(Map.Entry.comparingByValue())
  4. Map.Entry.<String, Integer>comparingByValue().reversed()
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Die Tickets gehören zu Ivo (2, 5), June (1, 7), Kai (1, 4), Luz (2, 5), wobei jedes Paar Gruppe und Punktzahl angibt. Welche Reihenfolge ergibt sich nach aufsteigender Gruppe, absteigender Punktzahl und dann aufsteigendem Namen?
  1. [Kai, June, Ivo, Luz]
  2. [Ivo, Luz, June, Kai]
  3. [June, Kai, Ivo, Luz]
  4. [June, Kai, Luz, Ivo]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Was ergibt dies, und welche Wörter werden vom Filter geprüft? Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()
  1. Optional[5]; ash und cedar werden geprüft.
  2. Optional[4]; alle vier Wörter werden geprüft.
  3. Optional[5]; alle vier Wörter werden geprüft.
  4. Optional[3]; nur ash wird geprüft.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine Liste hat die verschachtelten Gruppen [2, 5], [] und [5, 8]. Was gibt groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum() zurück?
  1. 18
  2. 15
  3. 20
  4. 13
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Die Werte sind -9, -4 und -12. Was gibt values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0) zurück?
  1. 0
  2. -8
  3. -4
  4. -18
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein Sale hat ein Team und eine ganzzahlige Stückzahl. Ignoriere Verkäufe mit null oder negativer Stückzahl, berechne den Mittelwert jedes Teams aus seinen verbleibenden Verkäufen und gib das Team mit dem höchsten Mittelwert oder bei leerer Eingabe „none“ zurück. Nimm an, dass es keine gleichen Mittelwerte gibt. Welche Pipeline passt? Die Collector-Methoden sind statisch importiert.
  1. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, summingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  2. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  3. sales.stream().collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().filter(e -> e.getValue() > 0).max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  4. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByKey()).map(Map.Entry::getKey).orElse("none")
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Welcher Wert steht für die Wörter "an", "oak", "to", "elm" auf der true-Seite von partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList()))?
  1. [AN, TO]
  2. [OAK, ELM]
  3. [AN, TO, ELM]
  4. [AN, OAK, TO, ELM]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

87 Schlüsselbegriffe, jeweils mit Definition

A

Assoziativität
Assoziativität bedeutet, dass eine andere Gruppierung der Eingaben einer Operation deren Ergebnis nicht ändert. Sie wird für den Akkumulator einer Stream-Reduktion benötigt.
Durch Assoziativität eignet sich die Addition für eine Ganzzahlreduktion, während die Subtraktion ungeeignet ist.
Ausdrucksrumpf
Ein Ausdrucksrumpf ist ein Lambda-Rumpf in Form eines einzelnen Ausdrucks, dessen Wert das benötigte Ergebnis ohne explizites return liefert.
Der Ausdrucksrumpf x + 1 ermöglicht es x -> x + 1, die erhöhte Ganzzahl direkt zurückzugeben.
Autoboxing
Autoboxing ist Javas automatische Umwandlung eines primitiven Werts in das entsprechende Wrapperobjekt, wenn ein Referenzwert benötigt wird.
Autoboxing ermöglicht es, das Literal 5 an einen Konstruktor zu übergeben, der einen Integer erwartet.

B

Begegnungsreihenfolge
Eine Begegnungsreihenfolge ist die festgelegte Folge, in der eine Quelle ihre Elemente bereitstellt, etwa die Listenreihenfolge oder die Schlüsselreihenfolge einer sortierten Map.
Die Begegnungsreihenfolge bewirkt, dass findFirst den ersten passenden Namen in einer Quellliste auswählt.
BinaryOperator
Ein BinaryOperator ist eine Funktion, die zwei Werte desselben Typs zu einem Ergebnis desselben Typs verbindet.
Ein BinaryOperator<Integer> wie Integer::sum kann Anzahlen für einen wiederholten Map-Schlüssel zusammenführen.
Blockrumpf
Ein Blockrumpf ist ein in geschweifte Klammern eingeschlossener Lambda-Rumpf; wenn ein Ergebnis benötigt wird, müssen seine Pfade Werte ausdrücklich zurückgeben.
Ein Blockrumpf verwendet return a; und return b;, um eine Maximumoperation zu implementieren.

C

Collection-Fabrik
Eine Collection-Fabrik stellt einem Collector eine bestimmte Collection-Implementierung bereit, damit das Ergebnis das benötigte Verhalten oder die benötigte Ordnung hat.
Die Collection-Fabrik ArrayList::new fordert ausdrücklich eine Liste an, die späteres Hinzufügen erlaubt.
Collector
Ein Collector beschreibt, wie die Elemente eines Streams zu einem Ergebnis gesammelt oder zusammengefasst werden, etwa zu einer Liste, einem String, einer Map oder einer gruppierten Berechnung.
Ein Collector kann die Ergebnisliste direkt erstellen, statt eine externe Liste in forEach zu ändern.
Comparator
Ein Comparator ist eine separate Ordnungsregel, deren Methode compare für zwei Eingabewerte ein negatives Ergebnis, null oder ein positives Ergebnis zurückgibt.
Ein Comparator<String> kann Wörter nach Länge ordnen, ohne ihre natürliche String-Ordnung zu ändern.
Comparator mit Nullbehandlung
Ein Comparator mit Nullbehandlung weist null-Werten eine ausdrückliche Position zu, meist indem er eine Ordnung für Werte ungleich null mit nullsFirst oder nullsLast umschließt.
Ein Comparator mit Nullbehandlung für Namen behandelt ein vorhandenes Studierendenobjekt, dessen extrahierter Name null ist.
Comparator-Umkehrung
Eine Comparator-Umkehrung kehrt die vollständige Ordnung des Comparators vor dem Aufruf von reversed() um, einschließlich vorhandener sekundärer Vergleichskriterien.
Eine Comparator-Umkehrung am Ende einer Verkettung nach Gruppe und Punktzahl kehrt sowohl Gruppen- als auch Punktreihenfolge um.
Consumer
Ein Consumer ist ein funktionales Standardinterface für eine Aktion auf einer Eingabe; seine Methode accept gibt keinen Wert zurück.
Ein Consumer<String> kann jedes Wort ausgeben, wenn er an forEach einer Collection übergeben wird.

D

Diamantoperator
Der Diamantoperator ist <> in einem generischen Konstruktoraufruf. Er veranlasst den Compiler, Typargumente abzuleiten und dabei die Typprüfungen beizubehalten.
Der Diamantoperator in new ArrayList<>() erhält die Typisierung der Liste aus ihrem Kontext.
distinct
Die Operation distinct entfernt anhand der Objektgleichheit doppelte Elemente aus einem Stream und erhält bei einem geordneten Stream die Begegnungsreihenfolge.
Wendet man distinct vor dem Abbilden von Wörtern auf Längen an, können im Ergebnis dennoch wiederholte Längen bleiben.
Doppelter Schlüssel
Ein doppelter Schlüssel entsteht, wenn unterschiedliche Eingabeelemente gleiche Map-Schlüssel erzeugen. Dann ist eine Regel nötig, die einen gespeicherten Wert bestimmt.
Ein doppelter Schlüssel lässt toMap mit zwei Argumenten auch dann fehlschlagen, wenn beide zugeordneten Werte 85 sind.

E

Effektiv final
Einer effektiv finalen lokalen Variablen wird einmal und später kein weiterer Wert zugewiesen, sodass ein Lambda-Ausdruck sie ohne das Schlüsselwort final einfangen kann.
Eine effektiv finale Listenreferenz kann weiterhin auf eine Liste verweisen, der Elemente hinzugefügt werden.
Einfangen von Variablen
Das Einfangen von Variablen liegt vor, wenn ein Lambda-Ausdruck eine lokale Variable aus seinem umgebenden Code unter Beachtung von Javas Einschränkungen für finale Variablen verwendet.
Das Einfangen von Variablen ermöglicht es einem Predicate, jede Eingabe mit einem umgebenden Wert limit zu vergleichen.

F

filter
Die Operation filter behält Stream-Elemente, für die ihr Predicate wahr ist, und verwirft andere Werte, wobei der Elementtyp erhalten bleibt.
Ein filter nach Punkten muss vor dem Abbilden von Studierenden auf Namen ausgeführt werden, wenn er den Punkte-Getter benötigt.
flatMap
Die Operation flatMap wandelt jede Eingabe in einen Stream um und verbindet die Elemente dieser Streams zu einer Ausgabefolge.
Ein flatMap mit List::stream wandelt verschachtelte Integer-Listen in einen Stream von Ganzzahlen um.
Function
Eine Function ist ein funktionales Standardinterface, das über apply eine Eingabe vom Typ T in ein Ergebnis vom Typ R umwandelt.
Eine Function<String, Integer> kann vor dem Gruppieren jedes Wort in seine Länge umwandeln.
Funktionales Interface
Ein funktionales Interface hat eine zu implementierende abstrakte Methode; Deklarationen, die öffentlichen Instanzmethoden von Object entsprechen, zählen nicht mit.
Ein funktionales Interface wie Operation ermöglicht es dem aufrufenden Code, seine Berechnung mit einem Lambda-Ausdruck anzugeben.
Funktionskomposition
Funktionskomposition verbindet Umwandlungen so, dass eine das Ergebnis einer anderen erhält; andThen und compose legen entgegengesetzte Aufrufreihenfolgen fest.
Die Funktionskomposition in twice.andThen(inc) verdoppelt und erhöht dann, sodass aus 5 der Wert 11 wird.

G

Ganzzahlüberlauf
Ein Ganzzahlüberlauf tritt auf, wenn eine Ganzzahlrechnung den darstellbaren Bereich überschreitet und der Wert umläuft, wodurch sich ein Vergleichsvorzeichen umkehren kann.
Ein Ganzzahlüberlauf macht die Subtraktion großer Ganzzahlen mit unterschiedlichem Vorzeichen als Comparator-Implementierung unsicher.
Gebundene Methodenreferenz
Eine gebundene Methodenreferenz benennt eine Instanzmethode auf einem bestimmten Empfängerobjekt, das bei der Auswertung der Referenz festgelegt wird.
Die gebundene Methodenreferenz System.out::println gibt über dieses bestimmte Ausgabeobjekt aus.
Generische Klasse
Eine generische Klasse deklariert einen oder mehrere Typparameter, damit ihre Felder und Methoden vom aufrufenden Code gewählte Typbeziehungen bewahren können.
Die generische Klasse Box<T> unterstützt mit einer Deklaration eine String-Box und eine Integer-Box.
Generische Methode
Eine generische Methode deklariert ihre eigenen Typparameter vor ihrem Rückgabetyp, sodass bei jedem Aufruf zusammengehörige Eingabe- und Ergebnistypen gewählt werden können.
Die generische Methode last gibt für eine String-Liste einen String und für eine Integer-Liste eine Ganzzahl zurück.
Generisches Interface
Ein generisches Interface deklariert Typparameter, die die in seinem Methodenvertrag verwendeten Typen über verschiedene Implementierungen hinweg verbinden.
Ein generisches Interface Container<T> kann die Eingabe von put mit dem Ergebnis von take verknüpfen.
groupingBy
Der Collector groupingBy erstellt eine Map, die vorkommende Schlüssel der Klassifikationsfunktion Listen oder nachgeschalteten Zusammenfassungen der jeweils passenden Elemente zuordnet.
Ein groupingBy-Collector mit averagingInt speichert pro Studierendengruppe einen Mittelwert vom Typ double.

I

Indexbasierte Schleife
Eine indexbasierte Schleife verwendet eine explizite Positionsvariable für den Zugriff auf Elemente einer Folge und macht so benachbarte Positionen und Steuerungsentscheidungen sichtbar.
Eine indexbasierte Schleife kann list.get(i) mit list.get(i - 1) vergleichen und dabei zwei Zähler aktualisieren.
IntStream
Ein IntStream ist ein Stream primitiver int-Werte mit numerischen Operationen wie Summe und Mittelwert sowie Umwandlungen zurück zu Objekten.
Ein IntStream aus mapToInt kann Punkte summieren, ohne zuerst Wrapperwerte zu sammeln.
Invarianz
Invarianz bedeutet, dass zwischen Verwendungen eines generischen Typs mit unterschiedlichen Typargumenten nicht allein deshalb eine Untertypbeziehung besteht, weil ihre Typargumente eine solche Beziehung haben.
Invarianz verhindert die Verwendung von List<Integer> als List<Number> und schützt die Liste vor Schreibzugriffen mit Double-Werten.

J

joining
Der Collector joining fügt Zeichenfolgen zu einem String zusammen, optional mit Text zwischen den Elementen und um das gesamte Ergebnis.
Ein joining-Collector mit einem Komma als Trennzeichen erzeugt nach dem letzten Namen kein abschließendes Komma.

K

Klassifikationsfunktion
Eine Klassifikationsfunktion weist jedem Eingabeelement einen Gruppenschlüssel zu und bestimmt damit, welcher Map-Eintrag es erhält.
Die Klassifikationsfunktion String::length legt alle Wörter mit vier Buchstaben unter demselben ganzzahligen Schlüssel ab.
Konstruktorreferenz
Eine Konstruktorreferenz verwendet ClassName::new, um Verhalten zur Objekterzeugung bereitzustellen; die Argumente werden durch das Zielinterface bestimmt.
Die Konstruktorreferenz Student::new kann mit einem passenden Konstruktor aus einem Namen ein Studierendenobjekt erzeugen.
Kurzschlussauswertung
Kurzschlussauswertung ermöglicht es einer Operation zu enden, sobald ihr Ergebnis feststeht, ohne notwendigerweise jedes Quellelement zu verarbeiten.
Kurzschlussauswertung ermöglicht es anyMatch, true zurückzugeben, sobald ein Element das Predicate erfüllt.

L

Lambda-Ausdruck
Ein Lambda-Ausdruck liefert Parameter und einen Rumpf für die abstrakte Methode eines funktionalen Interfaces und verwendet dazwischen den Pfeil ->.
Der Lambda-Ausdruck (a, b) -> a * b liefert die Multiplikationsregel für eine Operation.
limit
Die Operation limit behält höchstens die angeforderte Anzahl von Elementen vom Anfang eines geordneten Streams, sodass spätere Eingaben übersprungen werden können.
Ein limit vor dem Sortieren wählt vom ursprünglichen Anfang aus, nicht die insgesamt höchsten Werte.

M

map
Die Operation map ersetzt jedes Eingabeelement durch ein berechnetes Ergebnis und kann so den Elementtyp des Streams ändern.
Ein map mit String::length wandelt Wörter in Integer-Wrapperwerte um, nicht in einen Stream primitiver Werte.
Map-Eintrag
Ein Map-Eintrag ist ein als ein Objekt betrachtetes Schlüssel-Wert-Paar. Dadurch können die Zuordnungen einer Map als Stream verarbeitet und nach Schlüssel oder Wert verglichen werden.
Ein Comparator für Map-Einträge kann die größte Bestandsmenge zuerst setzen, ohne die Quell-Map neu zu ordnen.
mapping
Der Collector mapping wandelt Elemente um, bevor er ihre Ergebnisse an einen anderen Collector weitergibt, häufig innerhalb einer Gruppe oder Partition.
Ein mapping-Collector kann innerhalb jeder Gruppe die Namen der Studierenden extrahieren, bevor eine Liste gesammelt wird.
Mehrfache Typschranken
Mehrfache Typschranken verbinden Anforderungen mit &, sodass ein Typargument jede Schranke erfüllen muss. Eine Klasse steht, falls vorhanden, an erster Stelle.
Die mehrfachen Typschranken in <T extends Number & Comparable<T>> verlangen numerisches Verhalten und Vergleichsverhalten.
Methodenreferenz
Eine Methodenreferenz benennt vorhandenes Methoden- oder Konstruktorverhalten mit :: und passt zu den Eingaben und dem Ergebnis eines funktionalen Interfaces als Zieltyp.
Die Methodenreferenz Integer::parseInt stellt einer Funktion von String zu Integer Parsen als Verhalten bereit.

N

Nach oben beschränkte Wildcard
Eine nach oben beschränkte Wildcard wie ? extends Number bezeichnet einen unbekannten Typ, der der angegebene Typ oder einer seiner Untertypen ist.
Eine nach oben beschränkte Wildcard ermöglicht es einer numerischen Summenberechnung, sowohl Integer- als auch Double-Listen zu lesen.
Nach unten beschränkte Wildcard
Eine nach unten beschränkte Wildcard wie ? super Integer bezeichnet einen unbekannten Typ, der der angegebene Typ oder einer seiner Obertypen ist.
Eine nach unten beschränkte Wildcard ermöglicht es Code, der Integer-Werte schreibt, eine modifizierbare List<Number> als Ziel zu akzeptieren.
Nachgeschalteter Collector
Ein nachgeschalteter Collector sammelt oder fasst die Elemente innerhalb jeder Gruppe oder Partition zusammen und bestimmt den Werttyp der Map.
Der nachgeschaltete Collector counting() wandelt eine Map mit Gruppenlisten in eine Map mit Anzahlen vom Typ Long um.
Natürliche Ordnung
Eine natürliche Ordnung ist die Ordnung, die eine Klasse über Comparable.compareTo definiert und die verwendet wird, wenn kein separater Comparator angegeben ist.
Die natürliche Ordnung von Strings setzt das großgeschriebene Wort "Cherry" vor das kleingeschriebene "apple".
Neutrales Element
Ein neutrales Element ist ein Wert, der einen anderen Wert bei der Operation einer Reduktion unverändert lässt, etwa null bei der Addition oder eins bei der Multiplikation.
Das neutrale Element 1 ermöglicht es einer Produktreduktion, ihren ersten Faktor zu erhalten, statt das Ergebnis auf null zu setzen.
Nicht modifizierbare Liste
Eine nicht modifizierbare Liste lehnt Operationen ab, die ihre Elemente oder Struktur über die Liste ändern, ohne ihre Elementobjekte unveränderlich zu machen.
Die nicht modifizierbare Liste aus Stream.toList() wirft eine Ausnahme, wenn Code versucht, ein weiteres Element hinzuzufügen.
Nichtinterferenz
Nichtinterferenz bedeutet, dass das Verhalten einer Pipeline ihre Quelle während der Verarbeitung unverändert lässt und so einen gültigen Durchlauf erhält.
Nichtinterferenz wird verletzt, wenn eine Stream-Aktion neue Elemente zu ihrer Quell-ArrayList hinzufügt.

O

Obere Typschranke
Eine obere Typschranke beschränkt einen Typparameter auf einen benannten Typ und dessen Untertypen und macht die Operationen dieses Typs in generischem Code verfügbar.
Die obere Typschranke in <T extends Number> erlaubt Aufrufe von doubleValue() auf einem T.
Optional
Ein Optional ist ein Container, der entweder einen Wert oder keinen Wert darstellt und es einer Methode ermöglicht, ein möglicherweise fehlendes Ergebnis auszudrücken.
Ein Optional aus einem gefilterten Maximum kann leer sein, sodass ein Ersatzwert den fehlenden Fall behandelt.
OptionalDouble
Ein OptionalDouble stellt einen primitiven double-Wert dar, der fehlen kann, wie beim Mittelwert eines leeren numerischen Streams.
Ein OptionalDouble aus average() kann über orElse einen gewählten Ersatzwert 0.0 liefern.

P

partitioningBy
Der Collector partitioningBy teilt Elemente anhand eines Predicate auf und gibt immer beide booleschen Schlüssel zurück, auch wenn eine Seite leer ist.
Ein partitioningBy-Collector kann eine leere Liste für true zurückgeben, wenn kein Studierender den vom Predicate geprüften Schwellenwert erreicht.
PECS-Regel
Die PECS-Regel dient zur Wahl von extends-Wildcards für Quellen von Werten und super-Wildcards für Ziele von Werten.
Die PECS-Regel hilft bei der Wahl der Quell- und Zielparameter einer Listenübertragung.
peek
Die Operation peek gibt Elemente unverändert weiter und führt für durchlaufene Werte eine Untersuchungsaktion aus; ihre Aufrufe können übersprungen werden.
Ein peek zur Ausgabe von Werten ist für erforderliche Arbeit unzuverlässig, weil die Ausführung seine Stufe auslassen kann.
Pipeline
Eine Pipeline verbindet eine Quelle, keine oder mehrere Zwischenoperationen und eine Terminaloperation, die ein Ergebnis liefert oder eine Aktion ausführt.
Die Pipeline von stream über filter zu count liefert die Anzahl der Treffer als long-Wert.
Predicate
Ein Predicate ist ein funktionales Standardinterface für eine Bedingung, das einen Wert entgegennimmt und über test einen boolean zurückgibt.
Ein an removeIf übergebenes Predicate<String> bestimmt passende Wörter, die gelöscht werden sollen.
Prüfoperation
Eine Prüfoperation gibt zurück, ob mindestens eines, alle oder keines der Elemente eines Streams ein Predicate erfüllen, und kann enden, sobald die Antwort feststeht.
Eine Prüfoperation auf leerer Eingabe ergibt false für anyMatch, aber true für allMatch.

R

Reduktion
Eine Reduktion verbindet Stream-Elemente mithilfe eines Akkumulators zu einem einzelnen Ergebnis, optional ausgehend von einem neutralen Element.
Eine Reduktion mit Addition und null berechnet eine Summe, ohne einen externen lokalen Zähler zu aktualisieren.
Roher Typ
Ein roher Typ ist eine generische Klasse oder ein generisches Interface, das ohne Typargumente verwendet wird. Dabei werden einige Prüfungen umgangen, die eine parametrisierte Verwendung durchsetzen würde.
Ein roher Typ in List xs kann einen fehlerhaften Schreibzugriff erlauben, den eine List<String> ablehnen würde.

S

Schlüsselextraktor
Ein Schlüsselextraktor ist eine Funktion, die den zum Vergleich eines Objekts verwendeten Wert auswählt, etwa seinen Namen, seine Gruppe oder seine numerische Punktzahl.
Mit dem Schlüsselextraktor Student::getPoints lässt sich durch comparingInt ein numerischer Comparator für Studierende erstellen.
Seiteneffekt
Ein Seiteneffekt ist eine Änderung außerhalb des Rückgabewerts einer Berechnung, etwa das Ändern einer eingefangenen Liste oder eines Felds des umgebenden Objekts.
Ein in einer Pipeline versteckter Seiteneffekt kann das Ergebnis von Aktualisierungen außerhalb ihrer Eingabewerte abhängig machen.
Sekundäres Vergleichskriterium
Ein sekundäres Vergleichskriterium ist eine zusätzliche Vergleichsregel, die nur verwendet wird, wenn alle vorherigen Regeln die beiden Elemente als gleich einordnen.
Ein sekundäres Vergleichskriterium nach Namen setzt Ada vor Chen, nachdem ihre gleichen Punktzahlen im ersten Vergleich einen Gleichstand ergeben haben.
Signatur nach Typlöschung
Eine Signatur nach Typlöschung ist eine Methodensignatur, deren Parametertypen nach der Typlöschung betrachtet werden. Sie hilft festzustellen, ob Überladungen in Konflikt stehen.
Die Signatur nach Typlöschung show(Set) ist bei den versuchten Überladungen mit Set<String> und Set<Long> gleich.
skip
Die Operation skip verwirft die angeforderte Anzahl anfänglicher Elemente eines geordneten Streams und gibt die übrigen Elemente weiter.
Nach skip(2) auf dem Bereich 1 bis einschließlich 10 ist der nächste ausgegebene Wert 3.
Spezialisierung für primitive Datentypen
Eine Spezialisierung für primitive Datentypen ist eine Variante eines funktionalen Interfaces, die primitive Werte direkt entgegennimmt oder zurückgibt und so die Umwandlung von Wrappern vermeidet.
Die Spezialisierung für primitive Datentypen ToIntFunction<String> liefert Längen direkt an mapToInt.
Stabile Sortierung
Eine stabile Sortierung erhält die ursprüngliche relative Reihenfolge von Elementen, deren Vergleich nach der verwendeten Ordnungsregel Gleichheit ergibt.
Eine stabile Sortierung behält Ada vor Chen, wenn beide 85 Punkte haben und Ada zuerst vorkam.
Statischer Kontext
Ein statischer Kontext gehört zur Klasse selbst und kann daher keinen Typparameter verwenden, der an die parametrisierten Instanzen dieser Klasse gebunden ist.
In einem statischen Kontext führt eine Box-Fabrik ein eigenes <E> ein, statt das T der Klasse zu verwenden.
Stream
Ein Stream ist eine Folge, die aus einer Quelle durch Operationen verarbeitet wird; er speichert nicht auf jeder Stufe eine separate Collection von Elementen.
Ein Stream aus der Liste points kann die Werte zählen, die eine Prüfung bestehen, und die Liste dabei unverändert lassen.
Supplier
Ein Supplier ist ein funktionales Standardinterface, dessen Methode get ohne Argumente einen Wert seines Ergebnistyps bereitstellt.
Ein Supplier<List<String>> mit ArrayList::new kann bei jedem Aufruf eine neue Liste bereitstellen.

T

Terminaloperation
Eine Terminaloperation beendet eine Stream-Pipeline, indem sie ein Ergebnis liefert oder eine Aktion ausführt, und verbraucht den Stream für diese Berechnung.
Die Terminaloperation count gibt einen long zurück und lässt denselben Stream für eine Wiederverwendung nicht verfügbar.
Transitivität
Transitivität verlangt, dass eine Ordnung a vor c setzt, wenn sie a vor b und b vor c setzt. So bleiben Vergleiche konsistent.
Transitivität verhindert, dass ein Comparator einen Zyklus zwischen drei geordneten Objekten erzeugt.
Typargument
Ein Typargument ist der tatsächliche Referenztyp, der bei der Verwendung einer generischen Klasse, eines generischen Interfaces oder einer generischen Methode angegeben wird.
Das Typargument String sorgt dafür, dass ein Getter von Box<String> Text ohne explizite Typumwandlung zurückgibt.
Typinferenz
Typinferenz ist der Vorgang, bei dem der Compiler ausgelassene Typinformationen aus Argumenten und dem umgebenden Zielkontext bestimmt.
Dank Typinferenz liefert last(words) einen String, wenn words eine Liste von Strings ist.
Typlöschung
Typlöschung ersetzt generische Typparameter nach der Prüfung durch ihre Typschranken oder durch Object; der Compiler fügt notwendige explizite Typumwandlungen ein.
Durch Typlöschung teilen sich String-Box- und Integer-Box-Objekte dieselbe Laufzeitklasse Box.
Typparameter
Ein Typparameter ist ein Name, der in einer generischen Deklaration eingeführt wird und für einen Typ steht, den der verwendende Code angibt.
Der Typparameter T gibt dem Feld und dem Getter einer Box denselben vom aufrufenden Code gewählten Typ.
Typzeuge
Ein Typzeuge ist ein explizites Methoden-Typargument, das bei einem Aufruf zwischen dem Punkt nach dem Empfänger und dem Methodennamen steht.
Der Typzeuge in Lists.<String>last(words) gibt die Typwahl der Methode ausdrücklich an.

U

UnaryOperator
Ein UnaryOperator ist eine Funktion, deren einzelne Eingabe und deren Ergebnis denselben Typ haben. Er wird häufig zum Ersetzen von Collection-Elementen verwendet.
Ein UnaryOperator<String> kann über replaceAll einer modifizierbaren Liste jedes Wort in Großbuchstaben umwandeln.
Unbeschränkte Wildcard
Eine unbeschränkte Wildcard ist ? ohne Typschranke. Sie steht für ein unbekanntes Argument und erlaubt das Lesen von Elementen nur als Object.
Eine unbeschränkte Wildcard in List<?> ermöglicht es einer Anzeigemethode, die Listengröße abzufragen, ohne ihren Elementtyp zu kennen.
Unchecked-Warnung
Eine Unchecked-Warnung meldet eine Operation, deren generische Typsicherheit der Compiler nicht vollständig nachweisen kann, oft aufgrund einer Verwendung als roher Typ oder einer expliziten Typumwandlung.
Eine Unchecked-Warnung bei add auf einer rohen Liste weist auf einen möglichen späteren Lesezugriff mit falschem Typ hin.
Ungebundene Methodenreferenz
Eine ungebundene Methodenreferenz benennt eine Instanzmethode über ihre Klasse und bezieht das Empfängerobjekt aus dem ersten Argument des funktionalen Interfaces.
Die ungebundene Methodenreferenz String::length ruft length() auf jedem übergebenen String auf.

V

Verzögerte Auswertung
Verzögerte Auswertung verschiebt die Verarbeitung, bis ein Ergebnis sie benötigt, und ermöglicht es einem Stream, nur so viele Elemente wie nötig zu verarbeiten.
Verzögerte Auswertung ermöglicht es findFirst, anzuhalten, sobald ein passendes Wort gefunden und umgewandelt wurde.

W

Wrapperklasse
Eine Wrapperklasse ist ein Referenztyp, der einen primitiven Wert darstellt und dadurch die Verwendung von Werten wie Ganzzahlen als generische Argumente ermöglicht.
Die Wrapperklasse Integer macht List<Integer> gültig, während List<int> abgelehnt wird.

Z

Zieltyp
Ein Zieltyp ist das erwartete funktionale Interface, das einem Lambda-Ausdruck oder einer Methodenreferenz die Parameter- und Ergebnistypen vorgibt.
Der Zieltyp Predicate<String> macht s -> s.isEmpty() zu einer booleschen Prüfung eines Strings.
Zusammenführungsfunktion
Eine Zusammenführungsfunktion verbindet zwei demselben Schlüssel zugeordnete Werte zu dem einen Wert, den eine Map für diesen Schlüssel behalten soll.
Die Zusammenführungsfunktion Math::max behält die größte Menge, wenn ein Bestandsschlüssel zweimal vorkommt.
Zwischenergebnis
Ein Zwischenergebnis ist ein benannter Wert zwischen Verarbeitungsstufen, etwa eine gruppierte Map, die eine spätere Pipeline abfragt.
Ein Zwischenergebnis mit mittleren Temperaturen nach Stadt wird nach dem Gruppieren gefiltert, um warme Städte auszuwählen.
Zwischenoperation
Eine Zwischenoperation fügt eine verzögert ausgewertete Verarbeitungsstufe hinzu und gibt einen weiteren Stream zurück, sodass vor einem Endergebnis ausgewählt oder umgewandelt werden kann.
Die Zwischenoperation filter wartet auf eine Terminaloperation, bevor sie Elemente prüft.

Über diese Einheit

Von StudeliFehler melden

Erstelle deine eigenen Lernmaterialien

Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.

Jetzt starten