- Leren
- Informatica & ICT
- Programmeren
- Java programmeren: klassen, overerving, collecties en streams
- Lambda-expressies en streams in Java
Generieke typen, lambda-expressies en streams in Java
BeginnerLes 11 van 11 in Cursus Java programmeren: klassen, overerving, collecties en streams
Wat je leert
- Gebruik typeparameters om klassen, interfaces en methoden te schrijven die met veel typen werken, terwijl de compiler elk gebruik controleert.
- Definieer en gebruik generieke klassenDeclareer klassen en interfaces met typeparameters, maak objecten met typeargumenten en leg uit welke typeveiligheid dit biedt vergeleken met het opslaan van
Object. - Schrijf generieke methodenDeclareer methoden met eigen typeparameters, roep die aan met afgeleide of expliciete typeargumenten en bepaal wanneer een methode in plaats van een klasse generiek moet zijn.
- Beperk typeparameters met typegrenzen en jokersGebruik begrensde typeparameters om methoden op een generiek type aan te roepen, leg uit waarom een
List<Integer>geenList<Number>is en kies tussen? extendsen? super. - Werk binnen de beperkingen van typewissingLeg uit hoe de compiler typeargumenten wist, welke bewerkingen generieke typen daardoor niet toestaan en waarom je ruwe typen beter kunt vermijden.
- Definieer en gebruik generieke klassen
- Schrijf lambda-expressies en methodereferenties voor functionele interfaces, gebruik de standaard functionele interfaces van Java en geef gedrag als waarde door aan methoden.
- Schrijf lambda-expressies voor functionele interfacesHerken functionele interfaces, schrijf lambda-expressies in hun korte vorm en blokvorm en leg uit hoe het doeltype een lambda-expressie betekenis geeft.
- Gebruik de standaard functionele interfacesKies uit Predicate, Function, Consumer, Supplier en verwante interfaces, combineer die en geef die door aan collectiemethoden zoals removeIf en replaceAll.
- Leg variabelen vast en schrijf methodereferentiesGebruik variabelen uit de omringende code binnen een lambda-expressie, pas de regel voor effectief final variabelen toe en vervang eenvoudige lambda-expressies door de vier soorten methodereferenties.
- Schrijf lambda-expressies voor functionele interfaces
- Sorteer lijsten volgens elke gewenste regel met comparators, van een zelfgeschreven compare-methode tot ketens met comparing, thenComparing en reversed.
- Schrijf een comparator en sorteer daarmeeImplementeer het contract van compare correct, sorteer lijsten en arrays met een comparator en kies tussen Comparable en Comparator.
- Combineer comparators met comparing, thenComparing en reversedBouw comparators op uit sorteersleutels, voeg aanvullende sorteercriteria toe, keer volgorden om, handel null-waarden af en sorteer de sleutel-waardeparen van een map.
- Schrijf een comparator en sorteer daarmee
- Bouw verwerkingspijplijnen met de Stream API die gegevens filteren, transformeren, sorteren en reduceren, en leg uit hoe een verwerkingspijplijn wordt uitgevoerd.
- Bouw een verwerkingspijplijn met streams en begrijp hoe die wordt uitgevoerdMaak streams uit collecties, arrays en getalbereiken, onderscheid tussenbewerkingen van eindbewerkingen en leg luie evaluatie, eenmalig gebruik en het ongewijzigd blijven van de bron uit.
- Filter elementen, transformeer die en verander hun volgordeGebruik filter, map, mapToInt, flatMap, distinct, sorted, limit en skip en wissel tussen objectstreams en primitieve streams.
- Reduceer een stream tot één resultaatBereken aantallen, sommen, gemiddelden, extrema en waar-of-onwaarantwoorden met eindbewerkingen en handel ontbrekende resultaten af met Optional.
- Bouw een verwerkingspijplijn met streams en begrijp hoe die wordt uitgevoerd
- Verzamel streamresultaten in lijsten, verzamelingen, maps en strings, groepeer en partitioneer gegevens met collectors en bepaal wanneer een stream of een lus geschikter is.
- Verzamel resultaten in lijsten, verzamelingen, maps en stringsGebruik toList, Collectors.toList, toSet, toCollection, joining en toMap en handel niet-wijzigbare resultaten en dubbele sleutels af.
- Groepeer en partitioneer gegevens met collectorsVerdeel gegevens in groepen met groupingBy en partitioningBy en vat elke groep samen met een vervolgcollector.
- Kies tussen streams en lussen bij een volledige taakLos een gegevensverwerkingstaak van begin tot eind op, vergelijk een oplossing met een stream met een lus en houd verwerkingspijplijnen vrij van neveneffecten.
- Verzamel resultaten in lijsten, verzamelingen, maps en strings
Lambda-expressies en streams in Java: uitleg
1Schrijf generieke klassen en methoden
Generieke code behoudt een nuttig verband tussen invoer, opgeslagen waarden en resultaten. Een klasse kan dat verband voor een object behouden, terwijl een methode het bij elke aanroep opnieuw kan kiezen. Met typegrenzen en jokers kunnen API's de benodigde typen accepteren zonder onveilige schrijfbewerkingen toe te staan. De compiler controleert die afspraken voordat hij typeparameters wist. Dat verklaart zowel de voordelen van generieke typen als de bewerkingen tijdens de uitvoering die ze niet kunnen ondersteunen.
1.1 Definieer en gebruik generieke klassen
Een container moet aangeven wat die bevat. Met een typeparameter kan één klasse strings, getallen of je eigen objecten bevatten en controleert de compiler elk gebruik voordat het programma wordt uitgevoerd.
Geef een klasse een typeparameter
In class Box<T> is T een typeparameter: een naam voor een type dat de aanroepende code opgeeft. Gebruik die voor velden, methodeparameters en resultaten.
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; }
}
De naam van de constructor is Box, zonder <T>. De declaratie na de klassenaam introduceert T voor de klassebody. Maak nu een doos:
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 is het typeargument. Voor deze variabele geeft get() een String terug en accepteert set een String. Bij het lezen is geen expliciete typeconversie nodig. De poging b.set(42) mislukt bij het compileren: “incompatible types: int cannot be converted to String”. De fout wijst de verkeerde schrijfbewerking aan voordat een latere leesbewerking kan mislukken.
Waarom geen Object opslaan?
Een Object-veld kan verwijzen naar een string of een geheel getal. Code die een string nodig heeft, moet het teruggegeven object expliciet naar dat type omzetten. Als een geheel getal is opgeslagen, werpt die conversie tijdens de uitvoering een ClassCastException op. Een Box<String> maakt het bedoelde verband onderdeel van het gedeclareerde type.
De diamant <> in new Box<>("tea") vraagt de compiler de typeargumenten af te leiden. De controles blijven behouden. Als je de punthaken helemaal weglaat, zoals in new Box("tea"), gebruik je een ruw type en verlies je controles met de typeparameter. Als je een constructie met een ruw type aan een getypeerde variabele toewijst, ontstaan waarschuwingen voor niet-gecontroleerde typeveiligheid; de getypeerde variabele blijft bij latere aanroepen haar eigen gedeclareerde typeargument afdwingen.
Gebruik wrappers en meerdere parameters
Een typeargument moet een referentietype zijn. Gebruik Box<Integer>, niet Box<int>. Java zet de literal 5 via autoboxing om in een Integer bij het construeren van new Box<Integer>(5) en zet die via unboxing terug wanneer een berekening een int nodig heeft. Als n die doos bevat, geeft int next = n.get() + 1; de waarde 6.
Twee onafhankelijke typen vereisen twee parameters. Een Pair<K, V> kan een veld K first en een veld V second hebben. Voor een Pair<String, Integer> geven de getters een String en een Integer terug. Gebruikelijke namen zijn T voor een type, E voor een element, K en V voor een sleutel en waarde, en R voor een resultaat.
Zet typeparameters op interfaces
interface Container<T> {
void put(T item);
T take();
}
Een klasse kan de keuze openlaten met class Shelf<T> implements Container<T>, of vastleggen met class NameList implements Container<String>. List<E> en Comparable<T> gebruiken hetzelfde idee. Een generieke klasse blijft één klasse: Box<String> en Box<Integer> beschrijven verschillende toepassingen die tijdens het compileren worden gecontroleerd, geen twee klassendeclaraties. Je kunt ze niet aan elkaar toewijzen omdat ze verschillende elementtypen beloven.
Belangrijkste punten
- Een typeparameter benoemt een type binnen een declaratie; een typeargument vult dat type bij gebruik in.
- Een generieke getter geeft het gedeclareerde elementtype terug zonder expliciete typeconversie.
- De diamant behoudt typecontroles; bij een ruw type gaan generieke controles verloren.
- Gebruik wrapperklassen zoals
Integerals typeargumenten. - Klassen en interfaces kunnen meer dan één typeparameter hebben.
1.2 Schrijf generieke methoden
Sommige bewerkingen hebben een typekeuze nodig voor één aanroep, niet voor de levensduur van een object. Een generieke methode introduceert haar eigen typeparameter en behoudt een verband tussen haar invoer en resultaat.
Declareer het type vóór het retourtype
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
}
}
Lees de declaratie in delen. static maakt er een klassenmethode van. <T> introduceert een typeparameter die bij de methode hoort. De volgende T is het retourtype. List<T> geeft aan dat de invoerlijst en het resultaat hetzelfde elementtype hebben.
Voor de eerste aanroep leidt Java Integer af voor T. Het resultaat kan dan via unboxing worden omgezet naar int. In de tweede aanroep is <String> een expliciet methodetypeargument, geplaatst tussen de punt en de methodenaam. Lists.last(...) zou hier hetzelfde type afleiden. Een expliciet methodetypeargument heeft iets vóór de punt nodig: een klassenaam voor een statische methode of een object zoals this voor een instantiemethode.
Deze methode verwacht een niet-lege lijst. Bij een lege lijst is size() - 1 gelijk aan -1, dus werpt get een IndexOutOfBoundsException op. Generieke typen controleren typen; ze bewijzen niet dat een index bestaat.
Verbind meerdere typen
Een generieke methode kan meerdere parameters introduceren. Gegeven een klasse Pair<K, V> met een constructor en getters, draait deze volledige methode de rollen om:
static <K, V> Pair<V, K> swap(Pair<K, V> p) {
return new Pair<>(p.getSecond(), p.getFirst());
}
Voor een invoerpaar ("Ada", 36) van het type Pair<String, Integer> is het resultaat (36, "Ada") van het type Pair<Integer, String>. De retourdeclaratie legt het verband vast; een retourtype Object zou het verbergen en de aanroepende code dwingen tot een expliciete typeconversie.
Een methode zoals <T> int countEqual(List<T> list, T target) brengt ook invoerwaarden met elkaar in verband. De lus kan elk element met target vergelijken via equals. Bij ["a", "b", "a", "c", "a"] en "a" is de telling 3. Typeafleiding kan voor argumenten een gemeenschappelijk supertype kiezen, dus het herhalen van T dwingt niet altijd af dat twee argumentexpressies identieke gedeclareerde typen hebben.
Bepaal waar de typeparameter bij hoort
Een Box<String> behoudt zijn typeargument bij gebruik van dat object. Lists.last kiest voor elke aanroep opnieuw een typeargument. Een gewone hulpklasse kan daarom generieke statische methoden bevatten zonder zelf generiek te zijn.
Een statische methode binnen Box<T> kan de T van de klasse niet gebruiken: de statische methode behoort tot de gedeelde klasse, niet tot één doos. Declareer een afzonderlijke parameter, bijvoorbeeld static <E> Box<E> of(E value). Als een methodedeclaratie begint met static T first(...) zonder een eigen <T> voor de methode, meldt de compiler in een gewone klasse dat hij T niet kan vinden. Voeg de declaratie vóór het retourtype toe; als je het retourtype verandert in Object, gaat het nuttige verband verloren.
Belangrijkste punten
- Een generieke methode declareert haar eigen typeparameters vóór het retourtype.
- Typeafleiding gebeurt voor elke aanroep afzonderlijk.
- Een expliciet methodetypeargument staat na een punt en vóór de methodenaam.
- Gebruik meerdere typeparameters wanneer invoer en uitvoer meerdere samenhangende rollen hebben.
- Een statische methode moet haar eigen typeparameter declareren.
- Typeveiligheid controleert niet of de invoer van een methode aan de eis van een niet-lege lijst voldoet.
1.3 Beperk typeparameters met typegrenzen en jokers
Een onbeperkte T ondersteunt alleen bewerkingen die op Object beschikbaar zijn. Typegrenzen voegen een nuttige garantie toe. Met jokers kan een parameter een reeks generieke typen accepteren zonder te doen alsof al hun elementtypen identiek zijn.
Vereis de bewerkingen die je nodig hebt
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;
}
De typegrens geeft aan dat een T zichzelf kan vergelijken met een andere T. De aanroep largest(List.of(3, 11, 7)) begint met 3, vervangt de beste waarde door 11 en laat die bij 7 ongewijzigd. De methode geeft 11 terug. Voor ["pear", "apple", "plum"] is "plum" het grootste woord in natuurlijke volgorde. Bij een lege lijst mislukt get(0).
extends introduceert ook typegrenzen voor interfaces. <T extends Number> maakt doubleValue() beschikbaar. Meerdere typegrenzen gebruiken &, met een eventuele klasse voorop: <T extends Number & Comparable<T>>.
Waarom List geen List is
Integer is een subklasse van Number, maar List<Integer> is geen subtype van List<Number>. Generieke typen zijn invariant. Als die toewijzing was toegestaan, zou code die de List<Number>-referentie gebruikt een Double kunnen toevoegen; de oorspronkelijke eigenaar zou die vervolgens als een Integer lezen. Een parameter List<Number> accepteert wel implementaties zoals ArrayList<Number>, maar wijst List<Integer> af.
Lees met extends; voeg toe met super
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 betekent één onbekend type dat Number of een subtype daarvan is. sum accepteert lijsten met gehele getallen, kommagetallen of getallen. De methode leest elk element als een Number. sum(List.of(1.5, 2.5)) geeft 4,0. De methode kan geen 3 toevoegen: het onbekende elementtype kan Double zijn. De typeregels staan het toevoegen van null toe, al kan een specifieke lijst dit tijdens de uitvoering afwijzen.
? super Integer betekent Integer of een supertype daarvan. De tweede methode kan gehele getallen toevoegen aan een wijzigbare List<Integer>, List<Number> of List<Object>. Een waarde die via die parameter wordt gelezen, heeft alleen het type Object: bestaande elementen kunnen andere getallen of objecten zijn.
| Parameter | Veilige bewerking met elementen |
|---|---|
List<? extends T> | Lees een element als T |
List<? super T> | Voeg een T toe; lees als Object |
List<T> | Lees en schrijf met hetzelfde benoemde type |
List<?> | Lees als Object; bekijk de grootte |
Het ezelsbruggetje is producent: extends, consument: super. Het beschrijft de richting waarin elementen worden doorgegeven, niet of de hele collectie onveranderlijk is. Zo kan het nog steeds mogelijk zijn om via een extends-weergave elementen te verwijderen.
Gebruik een benoemde typeparameter als een type meerdere posities moet verbinden, vooral een invoer- en een retourtype. Gebruik een joker als alleen een reeks geaccepteerde invoertypen van belang is. Collections.copy leest uit List<? extends T> en schrijft naar List<? super T>; de doellijst moet al genoeg posities hebben om te vervangen.
Belangrijkste punten
- Typegrenzen maken de methoden van het grenstype beschikbaar op
T. - Generieke invariantie beschermt een lijst tegen schrijfbewerkingen met het verkeerde elementtype.
- Gebruik
? extends Tom elementen alsTte lezen. - Gebruik
? super Tom waarden van het typeTtoe te voegen; gelezen waarden hebben het typeObject. - Een joker beperkt bewerkingen via een referentie; hij maakt het object niet onveranderlijk.
- Gebruik een benoemde typeparameter om invoer- en resultaattypen te verbinden.
Collections.copyvervangt bestaande posities in de doellijst.
1.4 Werk binnen de beperkingen van typewissing
Generieke typen geven de compiler meer informatie dan een gewone klassentest tijdens de uitvoering heeft. Als je begrijpt wat na het compileren overblijft, begrijp je ook waarschuwingen over ruwe typen en verschillende bewerkingen die Java afwijst.
Wat typewissing verwijdert
Java controleert typeargumenten en wist daarna typeparameters tot hun typegrenzen. Een onbegrensde T wordt Object; een T extends Number wordt Number. De compiler voegt waar nodig expliciete typeconversies in wanneer waarden worden gelezen. Tijdens de uitvoering is er één klasse Box, dus dit geeft true weer:
System.out.println(
new Box<String>("a").getClass()
== new Box<Integer>(1).getClass()
);
De declaratie van Box kan de generieke klasse zijn met een waardeveld, constructor en getter. Beide constructies hebben tijdens de uitvoering dezelfde klasse, ook al gelden voor hun gedeclareerde toepassingen verschillende controles tijdens het compileren.
Bewerkingen die tijdens de uitvoering een bekend type nodig hebben
Binnen een generieke klasse compileert new T() niet. Java weet niet welke constructor het type van de aanroepende code heeft. Accepteer een fabriek om de waarde te maken:
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]
}
}
De supplier levert het gedrag om de waarde te maken. Ook new T[10] en new List<Integer>[2] worden afgewezen omdat ze een generieke array proberen te maken. Sla een reeks op in ArrayList<T> als je geen arraygedrag nodig hebt.
Voor een Object obj kan obj instanceof List<String> het elementtypeargument niet veilig testen. Gebruik obj instanceof List<?> om te testen of het een lijst is en behandel de elementen daarna als onbekend. Die test stelt niet vast dat het elementtype string is.
Gedeelde statische leden en overladen methoden na typewissing
Een veld zoals static T shared kan de T van de omsluitende klasse niet gebruiken. Alle parametrisaties delen het statische lid. Een statische methode kan een eigen <E> introduceren, onafhankelijk van de klasseparameter.
Twee overladen methoden print(Set<String> s) en print(Set<Integer> s) kunnen niet naast elkaar bestaan in één klasse. Beide worden na typewissing een methode die Set accepteert, waardoor een naamconflict ontstaat. Geef de bewerkingen verschillende namen of ontwerp één methode die het gemeenschappelijke gedrag afhandelt.
Volg een waarschuwing voor niet-gecontroleerde typeveiligheid tot de verkeerde leesbewerking
List raw = new ArrayList();
raw.add("text");
raw.add(42);
for (Object value : raw) {
String text = (String) value;
System.out.println(text);
}
Dit geeft text weer, waarna de expliciete typeconversie van 42 een ClassCastException opwerpt. De declaratie met een ruw type kan een melding over het ruwe type opleveren; de toevoegingen leveren waarschuwingen over niet-gecontroleerde aanroepen op omdat generieke controles zijn omzeild. Als je de declaratie vervangt door List<String>, verschuift de fout naar add(42) tijdens het compileren. Een waarschuwing voor niet-gecontroleerde typeveiligheid wijst op een verloren garantie en een risico op mislukking tijdens de uitvoering. Herstel het gebruik van het ruwe type of de onveilige conversie in plaats van aan te nemen dat een latere leesbewerking veilig is.
Belangrijkste punten
- Typewissing vervangt typeparameters door hun typegrenzen, of door
Objectals ze onbegrensd zijn. - Geparametriseerde toepassingen delen tijdens de uitvoering één klasse.
- Gebruik een meegegeven fabriek in plaats van
new T(). - Parametertypen na typewissing kunnen generieke overladen methoden niet onderscheiden.
2Geef gedrag door met lambda-expressies
Een methode kan zowel gedrag als gegevens accepteren. Functionele interfaces beschrijven de vorm van dat gedrag en lambda-expressies leveren het werk dat moet worden uitgevoerd. De standaardinterfaces geven veelvoorkomende taken duidelijke namen: testen, transformeren, handelen en leveren. Met vastgelegde variabelen kan een regel informatie uit de omgeving gebruiken, terwijl methodereferenties bestaand gedrag benoemen. Door aan te sluiten op het doeltype blijft elk stuk syntaxis verbonden met een gewone methodeaanroep.
2.1 Schrijf lambda-expressies voor functionele interfaces
Een methode kan een regel als argument accepteren. Met een lambda-expressie schrijf je die regel waar je hem gebruikt, terwijl een functionele interface Java vertelt welke invoer de regel krijgt en wat die teruggeeft.
Voldoe aan één abstracte methode
Een functionele interface heeft één te implementeren abstracte methode. Declaraties die overeenkomen met publieke instantiemethoden van Object, zoals equals(Object), tellen niet mee. Default-methoden en statische methoden voegen geen abstracte methoden toe die moeten worden geïmplementeerd. Comparator<T> en Runnable zijn functionele 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
}
}
De interface levert de methodenaam apply, de twee parametertypen int en het resultaattype int. De lambda-expressie levert de parameternamen en de body. Ze maakt een implementatie die via Operation kan worden gebruikt; die implementatie wordt pas uitgevoerd wanneer het programma apply aanroept.
combine geeft zijn twee getallen altijd door aan op. De aanroepende code verandert het gedrag door optellen of vermenigvuldigen op te geven. Zo ontvangt ook sort een ordeningsregel en filter een voorwaarde.
Lees de syntaxis
Een lambda-expressie heeft parameters, -> en daarna een expressie of een blok. Bij één afgeleide parameter mogen de haakjes weg: w -> w.length() > 5. Bij nul parameters is () nodig en bij twee of meer zijn haakjes nodig, zoals in (a, b) -> a * b. Expliciete typen zijn toegestaan: (int a, int b) -> a + b.
Een expressiebody geeft het resultaat automatisch terug. Een blok dat een waarde oplevert, moet die teruggeven:
Operation max = (a, b) -> {
if (a > b) {
return a;
}
return b;
};
System.out.println(max.apply(3, 9)); // 9
Elk pad door dit blok geeft een int terug. Als je accolades rond a + b zet zonder return, ontstaat geen geldig blok dat een waarde teruggeeft.
Laat het doeltype de betekenis bepalen
Het doeltype is de functionele interface die wordt verwacht bij een toewijzing, argument of retourpositie. De tekst x -> x * 2 kan passen bij Function<Integer, Integer> of UnaryOperator<Integer>. De parameter- en resultaattypen komen uit dat doeltype. Object o = () -> 1; mislukt omdat Object geen functionele methode biedt.
@FunctionalInterface vraagt de compiler de interfacedefinitie te controleren. Het is een nuttige bescherming als iemand later een tweede afzonderlijke abstracte methode toevoegt. Zonder de annotatie kan zo'n interface nog steeds worden gedeclareerd, maar een lambda-expressie kan de twee afzonderlijke vereisten niet implementeren.
Een lambda-expressie kan ook conflicteren met lokale namen. Als de omringende methode al int a declareert, kan ze binnen dat bereik geen lambdaparameter met de naam a declareren. Kies een andere parameternaam. Vergeleken met een anonieme klasse die Comparator<String> implementeert, laat een lambda-expressie de herhaalde klasse- en methodedeclaratie weg; de berekening voor de ordening moet nog steeds aan het comparatorcontract voldoen.
Belangrijkste punten
- Een lambda-expressie levert de implementatie van de abstracte methode van een functionele interface.
- De doelinterface bepaalt de invoertypen, het resultaattype en de methodenaam.
- Expressiebodies geven rechtstreeks een waarde terug; blokken die een waarde teruggeven, hebben
returnnodig. @FunctionalInterfacecontroleert de interfacedefinitie.- Een lambdaparameter kan een omringende lokale variabele niet opnieuw declareren.
2.2 Gebruik de standaard functionele interfaces
Java heeft al namen voor veelvoorkomende vormen van gedrag. Kies een interface op basis van het aantal invoerwaarden en het soort resultaat en roep daarna de functionele methode aan om het gedrag uit te voeren.
Kies de vorm van invoer en uitvoer
| Interface | Functionele methode | Taak |
|---|---|---|
Predicate<T> | boolean test(T t) | Een voorwaarde testen |
Function<T, R> | R apply(T t) | Een waarde transformeren |
Consumer<T> | void accept(T t) | Een actie uitvoeren |
Supplier<T> | T get() | Zonder invoer een waarde leveren |
Deze interfaces staan in java.util.function. Voor strings past w -> w.length() > 5 bij een predicaat, String::length bij een functie die Integer teruggeeft en System.out::println bij een consument. Een leverancier zoals () -> new ArrayList<String>() maakt telkens wanneer get() wordt uitgevoerd een nieuwe lijst. Een voorwaarde die door filter wordt gebruikt, vereist Predicate<T>, ook al zou een Function<T, Boolean> in andere code ook een booleaanse waarde kunnen berekenen.
UnaryOperator<T> is een functie van T naar dezelfde T. BinaryOperator<T> combineert twee waarden van het type T tot een T. BiFunction<T, U, R>, BiPredicate<T, U> en BiConsumer<T, U> nemen twee invoerwaarden. Primitieve vormen vermijden boxing: IntPredicate.test(int), IntBinaryOperator.applyAsInt(int, int) en ToIntFunction<T>.applyAsInt(T).
Combineer regels in een bepaalde volgorde
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 vereist dat beide voorwaarden gelden, or dat minstens één geldt en negate keert het antwoord om. Predicate.not(p) ontkent ook een predicaat. andThen voert eerst de ontvanger uit; compose voert eerst zijn argument uit. Hier berekent de eerste keten eerst 10 en dan 11, terwijl de tweede eerst 6 en dan 12 berekent.
Geef een regel door aan een methode
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) geeft [9, 12, 15]. Met T werkt de methode voor verschillende elementtypen; het predicaat maakt het selectiegedrag herbruikbaar.
Voor een wijzigbare lijst [3, 8, 1, 10, 6] laat removeIf(n -> n < 5) [8, 10, 6] over. replaceAll(n -> n * 2) geeft daarna [16, 20, 12]. Deze methoden wijzigen de lijst; forEach accepteert een consument voor acties zoals weergeven. Kopieer een resultaat van List.of(...) naar een ArrayList voordat je bewerkingen gebruikt die de lijst wijzigen.
Voor maps accepteert forEach((key, value) -> ...) een BiConsumer. merge(word, 1, Integer::sum) slaat 1 op voor een nieuw woord en verhoogt een bestaande telling. computeIfAbsent(key, k -> new ArrayList<>()) maakt een waarde voor een ontbrekende sleutel of een sleutel die aan null is gekoppeld en geeft de resulterende waarde terug. Deze methoden houden de zoek- en bijwerkregel bij elkaar.
Belangrijkste punten
- Kies een functionele interface op basis van de vorm van invoer en resultaat.
andThenvoert eerst de ontvanger uit;composevoert eerst zijn argument uit.removeIftest een predicaat;replaceAllpast een unaire operator toe.mergecombineert een bestaande mapwaarde met een opgegeven waarde.computeIfAbsentkan een ontbrekende mapwaarde maken en opslaan.- Specialisaties voor primitieve typen werken rechtstreeks met primitieve waarden.
2.3 Leg variabelen vast en schrijf methodereferenties
Een lambda-expressie kan informatie uit de omringende methode gebruiken. Als de body alleen één bestaande methode aanroept, drukt een methodereferentie hetzelfde gedrag vaak directer uit.
Leg een lokale variabele vast
int limit = 10;
Predicate<Integer> small = n -> n < limit;
System.out.println(small.test(7)); // true
De lambda-expressie gebruikt het vastleggen van limit. Een vastgelegde lokale variabele moet final of effectief final zijn: eenmaal toegewezen en nooit opnieuw toegewezen. Als je ergens na die eerste toewijzing limit = 20; toevoegt, compileert het vastleggen niet meer, ook als de hertoewijzing vóór de lambda-expressie staat.
De regel geldt voor de lokale variabele, niet voor de hele toestand binnen het object. Dit compileert:
StringBuilder log = new StringBuilder();
Runnable action = () -> log.append("ran");
action.run();
System.out.println(log); // ran
log verwijst nog steeds naar dezelfde builder. Een veld van het omringende object bijwerken is ook toegestaan, maar verborgen wijzigingen kunnen gedrag moeilijk te volgen maken.
Als je numbers.forEach(n -> total += n) probeert met een lokale int total = 0, mislukt dat omdat de vastgelegde variabele verandert. Gebruik een lus als de taak een bijwerking is, of bereken een resultaat rechtstreeks met numbers.stream().mapToInt(n -> n).sum().
Lees de vier referentievormen
Een methodereferentie gebruikt ::, zonder argumentenlijst. De functionele interface als doeltype levert de invoer en het verwachte resultaat.
| Soort | Referentie | Equivalente lambda-expressie |
|---|---|---|
| Statische methode | Integer::parseInt | s -> Integer.parseInt(s) |
| Gebonden instantiemethode | System.out::println | x -> System.out.println(x) |
| Ongebonden instantiemethode | String::length | s -> s.length() |
| Constructor | ArrayList::new | () -> new ArrayList<>() |
Bij de gebonden referentie ligt de ontvanger al vast: de PrintStream waarnaar System.out verwijst. Bij de ongebonden referentie wordt het eerste functionele argument de ontvanger. Zo komt String::compareToIgnoreCase overeen met (a, b) -> a.compareToIgnoreCase(b): de eerste string is de ontvanger en de tweede is het methodeargument.
Stem de referentie af op een doeltype
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()); // []
}
}
Een constructorreferentie kan ook argumenten aannemen. Student::new past bij een Function<String, Student> als een passende constructor één String als argument neemt. Het doeltype bepaalt welke overladen constructor wordt geselecteerd.
Student::getName verwijst naar een instantiegetter op elke meegegeven student; er is geen statische getter voor nodig. Schrijf een lambda-expressie als er extra werk is, zoals in s -> s.getPoints() * 2 of w -> w.length() > 5. String::length() schrijven is ongeldig: de referentie benoemt gedrag zonder het daar aan te roepen.
Belangrijkste punten
- Een vastgelegde lokale variabele moet final of effectief final zijn.
- Een final-referentie kan nog steeds naar een veranderlijk object verwijzen.
- Bij een gebonden referentie ligt de ontvanger vast; een ongebonden referentie neemt die uit het eerste argument.
- Constructorreferenties gebruiken
ClassName::new. - Methodereferenties hebben geen aanroephaakjes.
3Orden objecten met Comparator
Dezelfde objecten kunnen verschillende nuttige volgorden nodig hebben: namen alfabetisch, punten van hoog naar laag of groepen met een aanvullend sorteercriterium op basis van scores. Comparators houden deze regels gescheiden van de natuurlijke volgorde van de klasse. Een correcte vergelijking moet consistent zijn in haar teken en ordeningsrelaties. Opbouwmethoden maken de sleutels zichtbaar, maar de plaats van een omkering en de afhandeling van gelijke waarden of null-waarden bepalen nog steeds het resultaat.
3.1 Schrijf een comparator en sorteer daarmee
De natuurlijke volgorde van een object is niet altijd de volgorde die een taak nodig heeft. Met een afzonderlijke Comparator<T> kun je dezelfde objecten volgens verschillende regels sorteren zonder hun klasse te wijzigen.
Lees het teken van compare
compare(a, b) geeft een negatief getal terug als a eerst komt, nul als beide dezelfde sorteerrang hebben en een positief getal als a na b komt. Alleen het teken telt. Een comparator geeft geen booleaanse waarde terug.
Comparable<T> hoort bij de klasse en levert haar natuurlijke volgorde via compareTo. Een comparator is een afzonderlijke waarde die aan een sorteermethode wordt doorgegeven. Strings hebben een natuurlijke tekenvolgorde, maar een comparator kan ze in plaats daarvan op lengte sorteren.
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]
}
}
De comparator haalt de lengtes op en vergelijkt de getallen. words.sort wijzigt de wijzigbare lijst en heeft retourtype void; de methode geeft geen nieuwe lijst terug. Het sorteren van een resultaat van List.of(...) werpt een UnsupportedOperationException op, dus maakt het voorbeeld eerst een kopie als ArrayList.
Vergelijk getallen veilig
Gebruik Integer.compare of Double.compare. Aftrekken kan door overloop het teken omkeren: 2_000_000_000 - (-2_000_000_000) levert als int −294.967.296 op. Daardoor komt het grotere getal ten onrechte eerst in oplopende volgorde. Integer.compare(2_000_000_000, -2_000_000_000) geeft correct 1 terug.
Als je een verschil van doubles expliciet omzet naar int, gaat ook informatie verloren. (int) (1.2 - 0.7) is 0, hoewel de eerste waarde groter is. Double.compare(1.2, 0.7) geeft een positief resultaat.
Houd de ordening consistent
Als je de invoerwaarden omdraait, moet het teken van de vergelijking omkeren. Als a vóór b komt en b vóór c, moet a vóór c komen. Waarden die bij vergelijking nul opleveren, moeten ten opzichte van elke andere waarde dezelfde sorteerrang hebben. Schending van deze regels kan een verkeerde volgorde opleveren of ervoor zorgen dat de sortering een IllegalArgumentException opwerpt met “Comparison method violates its general contract!”. Als je alleen 0 of 1 teruggeeft, kun je niet beide richtingen uitdrukken.
Java's lijstsorteringen en sorteringen van objectarrays met een comparator zijn stabiel: bij een gelijke sorteerrang blijft de oorspronkelijke volgorde behouden. Voor de studenten Ada 85, Ben 72, Chen 85, Dara 90 en Eli 64 geeft sorteren op oplopende punten de volgorde Eli, Ben, Ada, Chen, Dara. Ada blijft vóór Chen omdat hun scores gelijk zijn en Ada eerst stond.
De studentenvoorbeelden gebruiken deze klasse en een wijzigbare lijst:
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
Kies een manier om te sorteren
list.sort(cmp) en Collections.sort(list, cmp) wijzigen een wijzigbare lijst. list.sort(null) vraagt om de natuurlijke volgorde. Arrays.sort(array, cmp) accepteert een objectarray zoals Integer[]; voor een int[] is er geen overladen variant met een comparator.
De natuurlijke volgorde van strings vergelijkt UTF-16-code-eenheden. Hoofdletters A–Z komen vóór kleine letters a–z, dus ["banana", "Cherry", "apple"] wordt [Cherry, apple, banana]. String.CASE_INSENSITIVE_ORDER geeft voor die woorden [apple, banana, Cherry]. Kies de tekstregel expliciet wanneer hoofdlettergebruik de volgorde niet mag bepalen.
Belangrijkste punten
- Een comparator geeft een vergelijkingsresultaat met een teken terug, geen booleaanse waarde.
Comparablelevert de natuurlijke volgorde; een comparator levert een afzonderlijke volgorde.- Gebruik numerieke vergelijkingsmethoden om overloop en afkappen te vermijden.
- Stabiele sorteringen behouden bij een gelijke sorteerrang de oorspronkelijke volgorde.
- Een lijstsortering wijzigt een wijzigbare lijst en heeft retourtype
void. - Primitieve arrays accepteren geen comparator.
3.2 Combineer comparators met comparing, thenComparing en reversed
Een ordening met meerdere sleutels lees je het gemakkelijkst als een reeks beslissingen: vergelijk de hoofdsleutel en vergelijk daarna alleen een tweede sleutel als de eerste gelijk is. Opbouwmethoden voor comparators drukken die beslissingen rechtstreeks uit.
Haal een sleutel op en sorteer gelijke waarden verder
Voor een Student met getName(), getGroup() en getPoints() ordent Comparator.comparing(Student::getName) op naam. comparingInt(Student::getPoints) haalt een int op zonder boxing.
Comparator<Student> ranking =
Comparator.comparingInt(Student::getPoints)
.reversed()
.thenComparing(Student::getName);
Dit zet hoge punten eerst en sorteert bij gelijke punten de namen in oplopende volgorde. Voor Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64) geeft dit Dara, Ada, Chen, Ben, Eli. De naamvergelijking wordt alleen uitgevoerd bij gelijke punten.
Plaats reversed op het juiste niveau
reversed() keert alles om wat ervoor is opgebouwd. Als je de rangschikking na thenComparing omkeert, keren ook de namen binnen elke gelijke score om. Om groepen oplopend en punten binnen elke groep aflopend te ordenen, keer je alleen de puntencomparator om:
Comparator<Student> byGroup =
Comparator.comparing(Student::getGroup)
.thenComparing(
Comparator.comparingInt(Student::getPoints).reversed());
Het resultaat is Ada, Chen, Eli, Dara, Ben: groep A komt vóór B; binnen A komt 85 vóór 64. Als je .reversed() in plaats daarvan aan het einde zet, keren zowel de groepsvolgorde als de puntenvolgorde ten opzichte van de voorgaande keten om.
Kies regels voor tekst en null
Comparator.naturalOrder() volgt Comparable; reverseOrder() keert die volgorde om. Geef een sleutelcomparator op als een sleutel speciale behandeling nodig heeft, zoals 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]
}
}
Omhul de vergelijking op het niveau waar null voorkomt. nullsLast rond de hele studentencomparator handelt null-studentobjecten af. Een functie die een naam ophaalt en null teruggeeft, heeft een comparator met expliciete null-afhandeling voor de naamsleutel zelf nodig. Comparators voor natuurlijke volgorde en gewone sleutelcomparators handelen null-sleutels niet automatisch af.
Sorteer sleutel-waardeparen en los typeafleiding op
Een HashMap garandeert geen bronvolgorde. Maak een stream van de sleutel-waardeparen en kies expliciet een volgorde:
stock.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEach(System.out::println);
Voor rice=12, oats=3 en flour=7 geeft dit rice=12, flour=7, oats=3 weer, elk op een afzonderlijke regel. De sleutel-waardeparen in de stream worden geordend zonder stock opnieuw te ordenen. Voeg thenComparing(Map.Entry.comparingByKey()) toe als gelijke waarden een voorspelbare sleutelvolgorde nodig hebben.
Het expliciete <String, Integer> helpt bij de typeafleiding over de geketende aanroep heen. Zo kan Comparator.comparing(s -> s.getName()).reversed() ook s afleiden als Object. Gebruik Student::getName of (Student s) -> s.getName() om het ontbrekende type op te geven. Een benoemde comparator kun je opnieuw gebruiken bij het sorteren van lijsten, bij sorted, min en max op streams, of bij een geordende map of verzameling.
Belangrijkste punten
comparinghaalt een sleutel op; primitieve opbouwmethoden vermijden boxing van numerieke sleutels.thenComparingwordt alleen gebruikt als de voorgaande vergelijking gelijkheid oplevert.reversed()keert de al opgebouwde comparator om.- Handel null-waarden af waar ze voorkomen: bij hele objecten of opgehaalde sleutels.
- Het sorteren van een stream met sleutel-waardeparen verandert de volgorde van de bronmap niet.
4Verwerk collecties met streams
Een verwerkingspijplijn met streams zet een collectie om in een reeks verwerkingsbeslissingen. Volg de waarden en hun typen terwijl een filter selecteert, een omzetting ze verandert en een eindbewerking het antwoord maakt. Door luie evaluatie worden sommige elementen mogelijk nooit bekeken, terwijl sorteren de hele invoer nodig heeft. Numerieke streams en optionals maken resultaattypen expliciet, ook wanneer geen waarden overblijven of een numeriek totaal te groot is.
4.1 Bouw een verwerkingspijplijn met streams en begrijp hoe die wordt uitgevoerd
Een stream beschrijft hoe je een reeks waarden verwerkt. Een stream leest uit een bron, geeft elementen door tussenbewerkingen en eindigt met een eindbewerking die een resultaat oplevert of een actie uitvoert.
Herken de drie onderdelen
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() is de bron. filter is een tussenbewerking: die geeft een andere stream terug. count is de eindbewerking: die geeft een long terug. De waarden 72, 90 en 81 komen door het filter. Door via de stream te lezen zijn 45 en 58 niet uit points verwijderd.
Kies een bron
Collecties bieden stream(). Een map biedt streams via entrySet(), keySet() of values(). Arrays.stream(array) maakt een stream van arrayelementen; een int[] levert een IntStream op. Stream.of("a", "b", "c") levert opgesomde waarden.
IntStream.range(1, 5) bevat 1, 2, 3, 4, zonder de eindwaarde. rangeClosed(1, 5) bevat ook 5 en heeft als som 15. Stream.iterate(1, n -> n * 2) heeft geen vast eindpunt; .limit(5).toList() verzamelt [1, 2, 4, 8, 16]. Een begrensd resultaat is essentieel bij het verwerken van die oneindige reeks.
Volg luie evaluatie
Tussenbewerkingen worden lui geëvalueerd. Alleen names.stream().map(String::toUpperCase); schrijven voert de transformaties niet uit. Een eindbewerking start de verwerking en elementen worden alleen verwerkt als dat nodig is.
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
De eerste twee woorden komen niet door het filter. "three" komt erdoor, wordt "THREE" en voldoet aan findFirst. Het laatste woord is niet nodig. Dit is voortijdig stoppen zodra het resultaat vaststaat: het antwoord is bekend voordat de hele bron is gelezen.
sorted heeft alle invoer nodig voordat het de eerste gesorteerde waarde kan doorgeven. Sommige andere toestandsafhankelijke bewerkingen, zoals sequentieel distinct, kunnen waarden doorgeven zodra invoer binnenkomt. De plaats van een bewerking in de keten beïnvloedt hoeveel werk nodig is.
Maak een nieuwe stream voor een ander resultaat
Na een eindbewerking is de stream verbruikt. Als je die stream opnieuw gebruikt, wordt meestal een IllegalStateException opgeworpen met “stream has already been operated upon or closed”. Roep voor een andere verwerkingspijplijn opnieuw points.stream() aan. Een collectie is herbruikbare opslag; een stream is één verwerkingspad.
Een stream maken kopieert de elementen niet. Houd de bron ongewijzigd terwijl je die verwerkt. map produceert omgezette waarden; de bewerking schrijft die niet terug naar de lijst. Voor bewuste wijzigingen aan een lijst drukken methoden zoals replaceAll en removeIf die taak rechtstreeks uit. Gebruik één bewerking per regel, zodat de verwerkingsvolgorde en het resultaattype zichtbaar blijven.
Belangrijkste punten
- Een verwerkingspijplijn heeft een bron, tussenbewerkingen en één eindbewerking.
- Tussenbewerkingen worden lui geëvalueerd.
- Door voortijdig te stoppen zodra het resultaat vaststaat, hoeft de rest van een bron mogelijk niet te worden gelezen.
sortedmoet alle invoer zien voordat het gesorteerde uitvoer kan doorgeven.- Een verbruikte stream moet worden vervangen door een nieuwe stream uit de bron.
- Een stream maken kopieert noch wijzigt de bron.
4.2 Filter elementen, transformeer die en verander hun volgorde
Volg na elke bewerking zowel de waarden als hun type: een latere lambda-expressie ontvangt het huidige streamelement, niet het oorspronkelijke bronobject.
Selecteer vóór het transformeren
filter behoudt elementen die aan een predicaat voldoen. map vervangt elk element door een berekende waarde. Bij studenten met getters voor naam en punten moet het selecteren op punten gebeuren vóór de omzetting naar namen:
List<String> names = students.stream()
.filter(s -> s.getPoints() >= 80)
.map(Student::getName)
.sorted()
.toList();
Voor Ada 85, Ben 72, Chen 85, Dara 90 en Eli 64 levert dit [Ada, Chen, Dara] op. Vóór map is het elementtype Student. Daarna is het type String, zodat een getter voor punten niet langer beschikbaar is.
Ga over naar een primitieve stream
Stream<Integer> bevat referenties naar wrapperobjecten en heeft geen methode sum(). mapToInt(Integer::intValue) levert een IntStream op, die numerieke bewerkingen biedt. mapToInt(String::length) meet op dezelfde manier strings als primitieve lengtes.
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) verwijdert 1 en 2 uit de stream. limit(3) behoudt de volgende drie waarden. Als je die aanroepen omdraait, blijft alleen 3 over. boxed() keert terug naar Stream<Integer>, zodat de elementen als objecten kunnen worden verzameld. mapToObj maakt andere objecten van primitieve waarden; streams met long- en double-waarden hebben overeenkomstige conversiebewerkingen.
Verwijder duplicaten en kies een volgorde
distinct gebruikt objectgelijkheid. ["map", "filter", "map", "sorted", "filter"] wordt [map, filter, sorted], waarna sorted() [filter, map, sorted] geeft. Implementeer voor je eigen klassen consistente methoden equals en hashCode als overeenkomende veldwaarden als duplicaten moeten tellen.
sorted() gebruikt de natuurlijke volgorde. Geef een comparator op voor een andere volgorde of voor objecten zonder natuurlijke volgorde. Het sorteren van een geordende stream is stabiel. Als je de vijf studenten op aflopende punten sorteert en er twee neemt, selecteer je Dara en Ada: Ada staat in de oorspronkelijke lijst vóór Chen bij hun gelijke score. Als je limit(2) vóór het sorteren aanroept, worden alleen de oorspronkelijke eerste twee studenten bekeken.
Maak geneste reeksen vlak
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]
Elke binnenste lijst wordt een stream en flatMap voegt de elementen daarvan samen tot één stream. Als je hier flatMap vervangt door map, krijg je een Stream<Stream<Integer>>: één streamobject per binnenste lijst, zonder die vlak te maken.
peek geeft een waarde ongewijzigd door en kan die weergeven om fouten op te sporen. De actie wordt alleen uitgevoerd voor elementen die daadwerkelijk worden doorlopen en een implementatie kan een onnodige stap overslaan, bijvoorbeeld bij het optimaliseren van count. Gebruik de bewerking niet om noodzakelijk werk uit te voeren. Gebruik voor één grootste waarde max(comparator) in plaats van elk element te sorteren en er één te nemen.
Belangrijkste punten
filterbehoudt het elementtype;mapkan het veranderen.- Primitieve streams bieden numerieke bewerkingen die ontbreken in
Stream<Integer>. - De volgorde van
skip,limiten sorteren verandert de geselecteerde elementen. distinctvolgt gelijkheid; sorteren van een geordende stream behoudt de volgorde bij een gelijke sorteerrang.flatMapmaakt streams die uit de invoerelementen zijn geproduceerd vlak.peekis bedoeld voor inspectie, niet voor noodzakelijke effecten van het programma.
4.3 Reduceer een stream tot één resultaat
Kies de eindbewerking op basis van het resultaat dat je nodig hebt: een aantal, een getal, een waarheidswaarde, één element of een algemene combinatie. Lege invoer is van belang wanneer geen resultaatwaarde kan worden gekozen.
Bereken een getal
count() geeft een long terug. sum() op een primitieve stream geeft een getal van het primitieve type van de stream terug. Voor de punten [72, 45, 90, 58, 81] geeft filteren op minstens 60 en daarna optellen de waarde 243. Het gemiddelde van die drie waarden is 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
}
}
De tweede verwerkingspijplijn gebruikt een nieuwe stream. Een lege som is 0, maar een gemiddelde van lege invoer heeft geen waarde. average() geeft een OptionalDouble terug en orElse(0.0) levert een gekozen terugvalwaarde. Die standaardwaarde is een keuze in het programma, niet het wiskundige gemiddelde van een lege verzameling.
Een int-som kan overlopen. Het optellen van twee waarden van 2.000.000.000 levert als int −294.967.296 op. Zet vóór het optellen om: mapToInt(...).asLongStream().sum() geeft 4.000.000.000 als een long.
Handel één waarde of geen waarde af
Een Optional<T> bevat één waarde of geen waarde. min, max, findFirst, findAny en reductie zonder neutraal element op objectstreams geven optionals terug. points.stream().max(Integer::compare) geeft Optional[90]; als je de container weergeeft, staat de tekst van de wrapper erbij.
Gebruik orElse voor een terugvalwaarde, orElseGet voor een leverancier van een terugvalwaarde, ifPresent voor een actie of map om een aanwezige waarde te transformeren. Als je filtert op een student met meer dan 95 punten, de eerste gevonden student omzet naar een naam en orElse("nobody") toepast, krijg je "nobody" voor de gegevens van de vijf studenten. get() of orElseThrow() aanroepen op een lege optional werpt een NoSuchElementException op.
Stel een waar-of-onwaarvraag
anyMatch vraagt of minstens één waarde voldoet, allMatch of elke waarde voldoet en noneMatch of geen enkele waarde voldoet. Ze kunnen stoppen zodra het antwoord bekend is. Bij lege invoer is anyMatch onwaar, terwijl allMatch en noneMatch waar zijn: er is geen waarde die een van beide algemene uitspraken schendt.
Combineer met een neutraal element
Stream.of(3, 5, 7).reduce(0, Integer::sum) bouwt de waarden 0, 3, 8, 15 op. Begin voor vermenigvuldigen met 1 en gebruik (a, b) -> a * b om 105 te krijgen. Een neutraal element laat een waarde bij de bewerking ongewijzigd; 10 is geen neutraal element voor optellen en zou het totaal ten onrechte naar 25 verschuiven. De accumulator moet associatief zijn, zodat groepering het resultaat niet verandert.
Zonder neutraal element geeft reduce(Integer::max) de waarde Optional[7] en bij lege invoer een lege optional. forEach voert daarentegen een actie uit en heeft retourtype void; het is geschikt voor weergeven. Gebruik reductie of verzamelen als de taak is om een resultaatwaarde te produceren.
Belangrijkste punten
- Aantallen gebruiken
long; numerieke streamresultaten volgen hun primitieve type. - Een lege som is nul; een gemiddelde of extremum van lege invoer heeft geen waarde.
- Gebruik de bewerkingen voor het lege geval van een optional voordat je de waarde leest.
- Bij lege invoer zijn
allMatchennoneMatchwaar;anyMatchis onwaar. - Een reductie vereist een neutraal element en een associatieve accumulator.
- Zet om naar
longvoordat je waarden optelt waarvan het totaal buiten het bereik vanintvalt.
5Verzamel streamresultaten en pas die toe
Een bruikbaar streamresultaat kan een lijst, verzameling, map, string of samenvattende waarde per groep zijn. De collector bepaalt wat wordt opgeslagen en wat gebeurt als sleutels worden herhaald. Groeperen combineert selectieregels met samenvattingen door vervolgcollectors; partitioneren levert twee booleaanse groepen op. In een volledige taak kun je vervolgens de resulterende map bevragen. Door die verwerkingspijplijn met een lus te vergelijken, kun je leesbare code kiezen en het opbouwen van resultaten gescheiden houden van wijzigingen aan externe toestand.
5.1 Verzamel resultaten in lijsten, verzamelingen, maps en strings
Een collector zet een stream om in een bruikbare resultaatstructuur. Bepaal wat het resultaat moet ondersteunen: duplicaten, ordening, latere wijzigingen of dubbele mapsleutels. Verschillende collectors bieden verschillende garanties.
Kies een lijst of verzameling
stream.toList() geeft een niet-wijzigbare lijst in bronvolgorde terug. Elementen toevoegen of verwijderen werpt een UnsupportedOperationException op. collect(Collectors.toList()) verzamelt ook een lijst, maar garandeert geen specifieke implementatie of wijzigbaarheid. Vraag expliciet om een lijst die je gaat wijzigen:
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 verwijdert duplicaten en garandeert geen volgorde. toCollection(TreeSet::new) vraagt om een gesorteerde verzameling. Een lijst behoudt dubbele elementen; kies een verzameling als het resultaat unieke waarden weergeeft.
Voeg tekst samen
Collectors.joining(", ") voegt strings samen met een scheidingsteken, zonder scheidingsteken aan het einde. De vorm met drie argumenten voegt ook een voorvoegsel en achtervoegsel toe:
String names = Stream.of("Ada", "Chen", "Dara")
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(names); // [Ada, Chen, Dara]
De elementen moeten CharSequence-waarden zijn, meestal strings. Zet studenten vóór het samenvoegen om met Student::getName of getallen met String::valueOf. Samenvoegen geeft één string terug, geen lijst die er bij weergave alleen vergelijkbaar uitziet.
Kies mapsleutels en -waarden
Collectors.toMap(keyFunction, valueFunction) berekent uit elk element een sleutel-waardepaar. Met de vijf studenten Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64) geeft het koppelen van namen aan punten vijf sleutel-waardeparen zoals Ada=85 en Eli=64.
Bij groepssleutels ligt dat anders: meerdere studenten hebben dezelfde groep. toMap(Student::getGroup, Student::getPoints) met twee argumenten werpt een IllegalStateException op wanneer een sleutel wordt herhaald, ook als beide waarden 85 zijn. Een map heeft één resulterende waarde per sleutel nodig en deze vorm biedt geen regel om die te kiezen.
Geef een derde argument op om duplicaten samen te voegen:
Map<String, Integer> totals = students.stream()
.collect(Collectors.toMap(
Student::getGroup, Student::getPoints, Integer::sum));
Groep A krijgt 85 + 85 + 64 = 234; groep B krijgt 72 + 90 = 162. Als je Integer::sum vervangt door Math::max, blijven de hoogste punten per groep behouden, A=85 en B=90. Gebruik de regel die bij de taak past, niet een willekeurige eerste waarde.
toMap garandeert geen specifieke mapvolgorde. Een vierde argument kan daarom vragen, bijvoorbeeld TreeMap::new voor gesorteerde sleutels. toMap(Student::getName, Student::getPoints, (a, b) -> a, TreeMap::new) behoudt de eerste waarde bij dubbele namen en sorteert op naam. Een collector voorkomt ook dat je een externe lijst moet maken en die binnen forEach moet wijzigen; het resultaat wordt opgebouwd door de bewerking die bedoeld is om het te verzamelen.
Belangrijkste punten
Stream.toList()is niet-wijzigbaar.Collectors.toList()garandeert geen wijzigbare implementatie.- Gebruik
toCollectionwanneer een specifiek collectietype nodig is. joiningverzamelt tekst met een optioneel scheidingsteken, voorvoegsel en achtervoegsel.toMapmet twee argumenten wijst dubbele sleutels af, ook als de dubbele waarden gelijk zijn.- Een samenvoegfunctie lost herhaalde mapsleutels op.
- Vraag om een mapfabriek wanneer de ordening van belang is.
5.2 Groepeer en partitioneer gegevens met collectors
Een gegroepeerd resultaat beantwoordt voor elke sleutel een vraag. Een partitie beantwoordt een ja-neevraag met twee groepen. Een vervolgcollector bepaalt wat elke groep opslaat.
Groepeer op een berekende sleutel
groupingBy(classifier) maakt een map die elke aangetroffen sleutel koppelt aan een lijst met de bijbehorende elementen. Als je ["fig", "kiwi", "pear", "plum", "apple", "mango", "banana"] groepeert op woordlengte, krijgt lengte 4 de lijst [kiwi, pear, plum]. Ontbrekende lengtes hebben geen sleutel-waardepaar; als je er een opzoekt, krijg je null. Gebruik daarom getOrDefault(key, List.of()) als een lege lijst handig is.
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}
}
}
De classificatiefunctie kiest gehele getallen als sleutels. TreeMap::new ordent die sleutels. counting() vervangt de lijst van elke groep door de grootte ervan als een Long. Het resultaattype is daarom Map<Integer, Long>, geen map van gehele getallen naar gehele getallen.
Verander wat een groep opslaat
Bij de studenten Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64) geeft groeperen op Student::getGroup deze mogelijkheden:
| Vervolgcollector | Waarde onder A | Waardetype |
|---|---|---|
counting() | 3 | Long |
summingInt(Student::getPoints) | 234 | Integer |
averagingInt(Student::getPoints) | 78,0 | Double |
mapping(Student::getName, Collectors.toList()) | [Ada, Chen, Eli] | List<String> |
Een vervolgcollector werkt binnen elke groep. Voor A is het gemiddelde 234 gedeeld door 3, oftewel 78,0; het gemiddelde van B is 81,0. maxBy en minBy slaan een Optional op met het extremum van elke groep. Gewoon groupingBy garandeert geen specifiek maptype of een specifieke sleutelvolgorde; met de vorm met een mapfabriek kies je die expliciet.
Verdeel in twee antwoorden
Map<Boolean, List<String>> partition = students.stream()
.collect(Collectors.partitioningBy(
s -> s.getPoints() >= 80,
Collectors.mapping(Student::getName, Collectors.toList())));
De lijst voor waar is [Ada, Chen, Dara] en de lijst voor onwaar is [Ben, Eli]. Beide booleaanse sleutels zijn aanwezig, ook als één kant geen studenten bevat. Met counting() als vervolgcollector bevatten lege kanten 0 als een Long. groupingBy met een booleaanse classificatiefunctie maakt daarentegen alleen sleutels die voorkomen.
Verwerk de map die je hebt opgebouwd
Een gegroepeerd resultaat is een gewone map. Doorloop die met forEach((group, value) -> ...) of maak een stream van de sleutel-waardeparen. Voor de gemiddelden A=78,0 en B=81,0 geeft averages.entrySet().stream().max(Map.Entry.comparingByValue()) een Optional<Map.Entry<String, Double>> terug met B=81.0. De optional bevat het winnende sleutel-waardepaar; het is niet de gewone waarde 81,0. Een afzonderlijke stap kan dat sleutel-waardepaar omzetten naar de sleutel of waarde en een lege map afhandelen.
Belangrijkste punten
- Een classificatiefunctie kiest groepssleutels; gewoon groeperen slaat lijsten met elementen op.
- Een vervolgcollector verandert de vorm van het resultaat van elke groep.
countingproduceertLong; collectors die gemiddelden berekenen, producerenDouble.- Een partitie bevat altijd beide booleaanse sleutels.
- Een ontbrekende groepssleutel geeft
nullterug, geen lege groep.
5.3 Kies tussen streams en lussen bij een volledige taak
Een volledige gegevenstaak heeft vaak twee fasen: vat de invoer samen en bevraag daarna de samenvatting. Kies streams als deze fasen natuurlijk lezen als standaardbewerkingen, en lussen als expliciete controle het werk duidelijker maakt.
Bereken gemiddelde metingen en selecteer daarna steden
Stel dat een Reading de methoden getCity() en getCelsius() heeft. De metingen zijn Oslo 4,5, Lima 19,0, Oslo 6,0, Cairo 27,5 en Lima 21,0, in graden Celsius.
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; }
}
Maak de invoerlijst vóór beide implementaties:
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();
Het eerste resultaat is {Cairo=27.5, Lima=20.0, Oslo=5.25}. Het gemiddelde van Oslo is (4.5 + 6.0) / 2, oftewel 5,25. Dat van Lima is 20,0. Het tweede resultaat is [Cairo, Lima]; de TreeMap zorgt voor een oplopende volgorde van steden. Filter de gemiddelden na het groeperen: als je eerst afzonderlijke metingen filtert, beantwoord je een andere vraag.
Gebruik voor de warmste afzonderlijke meting readings.stream().max(Comparator.comparingDouble(Reading::getCelsius)). Dit geeft een optional met de meting van Cairo terug, niet per definitie de stad met het warmste gemiddelde. Die taken kunnen bij andere gegevens verschillende antwoorden opleveren.
Druk dezelfde berekening uit met lussen
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}
Dit zijn alternatieve implementaties van hetzelfde resultaat; elke groep is niet-leeg omdat een meting die heeft gemaakt. De stream maakt het groeperen en het gemiddelde expliciet in één collector. De lussen laten de tussenliggende lijsten en de som zien.
Houd verwerkingsgedrag voorspelbaar
Een lambda-expressie in een verwerkingspijplijn hoort te rekenen met haar invoer zonder externe toestand te wijzigen. Voor de punten [72, 45, 90, 58, 81] levert filter(p -> p >= 60).toList() rechtstreeks [72, 90, 81] op. Als je binnen forEach aan een externe lijst toevoegt, verberg je het opbouwen van het resultaat in een actie.
Wijzig een ArrayList-bron niet binnen de eigen verwerkingspijplijn: elementen toevoegen vanuit een forEach kan een ConcurrentModificationException opwerpen. Verzamel nieuwe elementen eerst afzonderlijk en voeg die pas toe nadat de verwerking is voltooid. De typeregels van Java kunnen mutatie via een vastgelegde final-referentie toestaan; dat maakt mutatie binnen een stream nog niet geschikt.
Kies de duidelijkste besturingsstroom
Gebruik een lus wanneer je naast elkaar liggende indexen, een ingewikkelde stopvoorwaarde of meerdere onderling afgestemde bijwerkingen nodig hebt. Gebruik streams voor een leesbare selectie, transformatie en verzameling. Streamsyntaxis alleen biedt geen snelheidsgarantie. Geef een lang predicaat of een lange comparator een naam en splits een lange keten bij een betekenisvol tussenresultaat. Inspecteer bij het opsporen van fouten met peek of verzamel een tussenstap in een lijst en geef die weer; ga verder met een nieuwe stream van die lijst in plaats van een verbruikte verwerkingspijplijn opnieuw te gebruiken.
Belangrijkste punten
- Groepeer vóór het filteren wanneer de voorwaarde over groepsgemiddelden gaat.
- Een maximale afzonderlijke meting en een maximaal groepsgemiddelde beantwoorden verschillende vragen.
- Een collector geeft rechtstreeks aan hoe het resultaat wordt opgebouwd.
- Houd gedrag in een verwerkingspijplijn vrij van externe bijwerkingen en mutaties van de bron.
- Gebruik lussen wanneer indexen of afgestemde besturing de taak duidelijker maken.
- Inspecteer een verzamelde tussenstap en gebruik voor verder werk een nieuwe stream.
Lambda-expressies en streams in Java: voorbeelden
1Bouw een typeveilige herbruikbare API
17 stappen
- 1IntroductieBehoud het verband tussen typen
Een herbruikbare container moet onthouden wat de aanroepende code erin mag stoppen en wat ze eruit kan terugkrijgen. Een typeparameter legt dat verband vast in een declaratie.
Je bouwt een doos, verplaatst de typekeuze naar een methode en controleert daarna wat verandert wanneer een parameter een joker gebruikt. Vraag je bij elke stap af wat de compiler over een waarde weet voordat het programma wordt uitgevoerd.
- 2UitlegDeclareer de doos
Tbenoemt binnen deze klasse het type dat de aanroepende code kiest:class Box<T> { private T value; Box(T value) { this.value = value; } T get() { return value; } void set(T value) { this.value = value; } }Het veld, de invoer van de constructor, het resultaat van de getter en de invoer van de setter gebruiken allemaal dezelfde
T. De declaratie introduceert een plaatsaanduiding; er is nog geen specifiek opgeslagen type gekozen. - 3VoorbeeldGebruik één concreet type
Maak een doos en lees de waarde ervan:
Box<String> b = new Box<>("tea"); String drink = b.get(); b.set("water");Stringlevert het typeargument. De getter geeft een string terug zonder expliciete typeconversie en de setter accepteert strings. De diamant rechts vraagt Java de argumenten af te leiden. De controle verdwijnt daardoor niet; als je de punthaken helemaal weglaat, gebruik je een ruw type. - 4UitdagingVoorspel de afgewezen schrijfbewerking
De generieke
Box<T>heeftvoid set(T value). Zalb.set("nine")naBox<Integer> b = new Box<>(8);compileren? Leg uit waar het mislukt.---reveal---
Het mislukt bij het schrijven omdat deze referentie een
Integerverwacht, geenString. Er is geen latere leesbewerking nodig om de fout te ontdekken. De literal 8 kan via boxing worden omgezet inInteger; typeargumenten zelf moeten nog steeds referentietypen zijn, dusBox<int>is ongeldig. - 5VerbandKies voor elke aanroep een type
Een hulpmethode kan haar eigen typeparameter introduceren:
static <T> T last(List<T> list) { return list.get(list.size() - 1); }De eerste
<T>declareert de parameter en de volgendeTbenoemt het resultaattype. Een aanroep op[4, 8, 15]geeft deInteger15 terug; een aanroep op["x", "y"]geeft de string"y"terug. De methode vereist een niet-lege lijst, onafhankelijk van de typecontroles. - 6TipSchrijf een expliciet methodetypeargument na de punt
Meestal leidt Java het methodeargument af uit de opgegeven lijst. Een aanroep kan het ook expliciet opgeven:
String word = Lists.<String>last(words);Het expliciete methodetypeargument staat na de punt en vóór
last. Ook een statische methode binnen een generieke klasse heeft een eigen parameter nodig:static <E> Box<E> of(E value). DeTvan de klasse hoort bij geparametriseerd gebruik van instanties, terwijl de statische methode bij de gedeelde klasse hoort. - 7UitdagingVerbind invoer en uitvoer
Een hulpmethode moet een element uit een
List<E>teruggeven zonder dat de aanroepende code een expliciete typeconversie hoeft te schrijven. Een programmeur schrijftstatic E first(List<E> xs). Welke declaratie ontbreekt?---reveal---
Voeg
<E>toe vóór het retourtype:static <E> E first(List<E> xs). Dit introduceert een parameter van de methode en verbindt haar invoerelementen met haar resultaat. Als jeObjectals resultaat schrijft, gaat dat nuttige verband verloren in plaats van dat je het herstelt. - 8UitlegMaak vergelijken beschikbaar
Een onbeperkte
Theeft geen bekende bewerkingcompareTo. Een typegrens kan die garantie bieden:<T extends Comparable<T>>Een methode
largest(List<T>)kan dan eenT bestbijhouden, elkexmetbestvergelijken en die vervangen wanneerx.compareTo(best) > 0. Voor 3, 11 en 7 zijn de beste waarden 3, daarna 11 en daarna 11. Het retourtype blijft het eigen elementtype van de lijst. - 9KernpuntBescherm een lijst met gehele getallen
Een
Integeris eenNumber, maar eenList<Integer>is geenList<Number>.Als die lijsttoewijzing was toegestaan, zou code die de referentie naar de getallenlijst gebruikt een
Doublekunnen toevoegen. De oorspronkelijke eigenaar zou die vervolgens als eenIntegerlezen. Generieke invariantie voorkomt de toewijzing, zodat zowel lezen als schrijven aan de beloften blijft voldoen.ArrayList<Number>wordt geaccepteerd alsList<Number>omdat het elementargument nog steeds overeenkomt. - 10VoorbeeldZet gegevens in de veilige richting over
Met een extends-joker kan code lezen als een bekend boventype; met een super-joker kan ze een bekend ondertype toevoegen.
static <T> void append( List<? super T> dst, List<? extends T> src) { dst.addAll(src); }Met een wijzigbare
List<Number>als bestemming en eenList<Integer>als bron kan JavaIntegerkiezen voorT. Elk bronelement is acceptabel voor de bestemming, zonder te stellen dat beide lijsten identieke elementargumenten hebben. - 11UitdagingKies de veilige leesbewerking
Een parameter heeft het type
List<? super Integer>. Kan het eerste element rechtstreeks aan eenInteger-variabele worden toegewezen? Welk type is in plaats daarvan gegarandeerd?---reveal---
Alleen
Objectis gegarandeerd. De werkelijke lijst kan eenList<Number>ofList<Object>zijn en bestaande waarden bevatten die geen gehele getallen zijn. Via dit type is het veilig een geheel getal toe te voegen, maar die schrijfmogelijkheid zegt niet wat alle al opgeslagen elementen zijn. - 12UitlegOnderscheid controle van klassen tijdens de uitvoering
Typewissing vervangt typeparameters door hun typegrenzen en gebruikt
Objectwanneer geen typegrens is opgegeven. Java heeft het generieke gebruik dan al gecontroleerd en voegt bij het lezen de nodige expliciete typeconversies in.Een
Box<String>en eenBox<Integer>delen tijdens de uitvoering dezelfde klasseBox. Er is geen afzonderlijke klasse voor een doos met gehele getallen die een test tijdens de uitvoering kan herkennen. Generieke typen behouden typeverbanden bij de controle van de broncode. Daardoor verwijderen ze veel zichtbare expliciete typeconversies zonder de klasse voor elk argument te specialiseren. - 13VoorbeeldLever het gedrag om objecten te maken
Je kunt geen instantie van een typeparameter maken met
new T(). Vraag de aanroepende code in plaats daarvan om een fabriek:static <T> T create(Supplier<T> factory) { return factory.get(); } List<String> xs = create(ArrayList::new);De referentie kiest via het Supplier-doeltype een lijstconstructor. Generieke arrays zoals
new T[10]worden ook afgewezen; een collectie zoalsArrayList<T>is meestal geschikt voor de opslag. - 14TipVolg de waarschuwing over een ruw type
Een ruwe
Listkan"text"en 42 accepteren met waarschuwingen over niet-gecontroleerde aanroepen. Een latere expliciete typeconversie(String)mislukt wanneer die 42 bereikt.Als je de declaratie verandert in
List<String>, verschuift het probleem naar de ongeldige toevoeging. Een waarschuwing voor niet-gecontroleerde typeveiligheid betekent dat de compiler een garantie heeft verloren; de uitvoering kan voor sommige gegevens nog steeds slagen. Controleer het gebruik van het ruwe type of de expliciete typeconversie die de waarschuwing veroorzaakte, want de leesbewerking die uiteindelijk mislukt, kan ver weg staan. - 15UitdagingControleer de overladen methode na typewissing
Eén klasse declareert
show(Set<String> xs)enshow(Set<Integer> xs). Waarom kan Java de elementargumenten niet gebruiken om een overladen methode te kiezen?---reveal---
Beide parametertypen worden na typewissing
Set, dus de declaraties hebben dezelfde signatuur na typewissing. Geef ze verschillende namen of maak één gemeenschappelijke bewerking. Een test tijdens de uitvoering vanObjecttegenList<String>heeft een verwante beperking: testList<?>zonder iets over het elementargument te stellen. - 16UitdagingBekijk een herbruikbare API voor overdracht
Een API gebruikt een ruwe bronlijst, zet elk element expliciet om naar
Numberen voegt het toe aan een getallenlijst. De API moet gehele getallen en kommagetallen als bronnen accepteren. Welke bronparameter maakt die expliciete typeconversie overbodig en behoudt veilige numerieke leesbewerkingen?---reveal---
Gebruik
List<? extends Number>. Elk element kan alsNumberworden gelezen en zowel lijsten met gehele getallen als lijsten met kommagetallen passen.List<Number>zou die specifiekere lijstargumenten afwijzen. De bestemming moet wijzigbaar zijn; het generieke type alleen kan niet garanderen dat schrijfbewerkingen slagen. - 17SamenvattingLees een API aan de hand van haar beloften
Bepaal bij het controleren van een generieke declaratie waar elke typeparameter wordt geïntroduceerd, welke posities die verbindt en welke bewerkingen de typegrens biedt.
Markeer bij een overdracht de bron als producent en de bestemming als consument. Zoek bij het maken van een object naar meegegeven constructiegedrag. Vraag je bij een waarschuwing of afgewezen bewerking af of generieke informatie is omzeild of dat de bewerking tijdens de uitvoering een elementtype nodig zou hebben.
2Maak van gedrag een argument
14 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBegin met de vorm van het gedrag
- 2UitlegZoek de te implementeren methode
- 3VoorbeeldGebruik een blok als een beslissing dat vereist
- 4UitdagingHerstel het ontbrekende resultaat
- 5UitlegKoppel vier veelvoorkomende taken
- 6VoorbeeldLet op de volgorde van samenstellen
- 7VerbandLaat collecties de regel uitvoeren
- 8UitdagingOnderscheid verwijderen van selecteren
- 9UitlegLeg een waarde vast die niet opnieuw wordt toegewezen
- 10VoorbeeldLees de ontvanger uit de referentie af
- 11UitdagingControleer wat final beschermt
- 12VoorbeeldTel met een bijwerkregel voor een map
- 13UitdagingCombineer doeltypering en vastleggen
- 14SamenvattingVolg één aanroep door de regel
3Bouw een ordening stap voor stap op
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBenoem de volgorde die de taak nodig heeft
- 2UitlegLees een vergelijkingsresultaat
- 3VoorbeeldVermijd aftrekken bij numerieke vergelijkingen
- 4VoorbeeldBehoud bij gelijke scores de bronvolgorde
- 5UitdagingOnderscheid natuurlijke volgorde van taakgerichte volgorde
- 6UitlegVoeg alleen bij gelijkheid een regel toe
- 7KernpuntKeer alleen de bedoelde beslissingen om
- 8TipHandel null-waarden op het juiste niveau af
- 9UitdagingLees de volledige omkering
- 10VerbandOrden de sleutel-waardeparen, niet de map
- 11UitdagingMaak de volgorde van gelijke mapwaarden voorspelbaar
- 12SamenvattingControleer de vergelijkingsboom
4Volg waarden door een stream
16 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVolg waarden en typen samen
- 2UitlegBepaal het eindresultaat
- 3VoorbeeldStop na de eerste passende waarde
- 4UitdagingVoer een andere berekening uit
- 5VoorbeeldSelecteer studenten vóór het omzetten naar namen
- 6VerbandGa over naar numerieke bewerkingen
- 7VoorbeeldMaak vlak voordat je duplicaten verwijdert
- 8UitdagingLees de volgorde van bewerkingen
- 9VoorbeeldBereken een som en een gemiddelde
- 10UitlegHoud afwezigheid gescheiden van een waarde
- 11UitdagingHandel een ontbrekend maximum af
- 12KernpuntControleer algemene tests bij lege invoer
- 13VoorbeeldKies een neutrale beginwaarde
- 14TipVerbreed voordat overloop optreedt
- 15UitdagingCombineer vlak maken, selectie en een resultaat
- 16SamenvattingControleer de randgevallen van het resultaat
5Bouw een bruikbare gegevenssamenvatting
15 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieWerk terug vanuit het gewenste resultaat
- 2UitlegMaak latere wijzigingen tot een expliciete eis
- 3VoorbeeldLos botsingen op met een samenvoegregel
- 4UitdagingKies één waarde per herhaalde sleutel
- 5VerbandVoeg waarden samen als tekst
- 6UitlegKies een sleutel en daarna een groepsresultaat
- 7VoorbeeldVat gegevens binnen de studentengroepen samen
- 8KernpuntBehoud beide kanten van een partitie
- 9UitdagingVoorspel een lege kant
- 10VoorbeeldBereken eerst stadsgemiddelden
- 11VoorbeeldBevraag de samenvatting die je hebt opgebouwd
- 12TipMaak de besturingsstroom zichtbaar als dat helpt
- 13UitdagingBescherm de bron tijdens de verwerking
- 14UitdagingZoek de winnende groep veilig
- 15SamenvattingBekijk de hele gegevenstaak
Lambda-expressies en streams in Java: oefenvragen
Set 1: Gebruik de basis10 Oefenvragen
Vraag 1 van 10
Box<T> heeft T get() en void set(T value). Wat is voor Box<String> label = new Box<>("north"); het gedeclareerde resultaattype van label.get()?Toon antwoordVerberg antwoord
String vervangt de klasseparameter T in het gedeclareerde resultaat van de getter. De compiler behandelt de aanroep daarom als een aanroep die een string teruggeeft, zonder expliciete typeconversie door de aanroepende code. Typewissing maakt het resultaat op bronniveau niet Object; de controle tijdens het compileren gebruikt nog steeds het argument.Vraag 2 van 10
Toon antwoordVerberg antwoord
E vóór haar retourtype en herhaalt die voor de invoerelementen en het resultaat. Daardoor krijgt elke aanroep een eigen afgeleid typeargument. Alleen E op de parameter- en retourposities schrijven declareert die niet, dus de versie zonder <E> mislukt in een gewone hulpklasse.Vraag 3 van 10
doubleValue() aan te roepen. Ze moet zowel List<Integer> als List<Double> accepteren. Welk parametertype past?Toon antwoordVerberg antwoord
Number, zodat elke gelezen waarde als Number kan worden behandeld. List<Number> kan geen List<Integer> accepteren omdat generieke typen invariant zijn. Een super-joker zou ruimere elementtypen toestaan, maar gelezen waarden zouden alleen het type Object hebben.Vraag 4 van 10
Function<Integer, Integer> moet zijn invoer verdubbelen met een blokbody. Welke lambda-expressie past?Toon antwoordVerberg antwoord
return x * 2; levert het gehele resultaat dat de functie vereist. Een lambda-expressie met een expressiebody kan eenvoudig x -> x * 2 gebruiken, maar als je accolades toevoegt, blijft dat automatische expressieresultaat niet behouden.Vraag 5 van 10
int limit = 4; List<String> notes = new ArrayList<>(); Predicate<String> p = s -> { notes.add(s); return s.length() > limit; }; limit++;. Welke aanpassing behoudt deze eisen?Toon antwoordVerberg antwoord
Vraag 6 van 10
compare(a, b) aan bij een oplopende volgorde?Toon antwoordVerberg antwoord
Vraag 7 van 10
names.stream().filter(s -> s.length() > 2) op en doet niets met de teruggegeven stream. Wanneer wordt het predicaat aangeroepen?Toon antwoordVerberg antwoord
filter is een tussenbewerking die een lui uit te voeren verwerkingsstap vastlegt. Hier start geen eindbewerking het doorlopen, dus wordt het predicaat niet aangeroepen. Een stream maken kopieert of filtert de bronlijst niet direct en een latere, niet-gerelateerde leesbewerking van de lijst voert deze verwerkingspijplijn niet uit.Vraag 8 van 10
reduce(0, (a, b) -> a * b). Wat veroorzaakt het verkeerde resultaat?Toon antwoordVerberg antwoord
Vraag 9 van 10
add ondersteunen. Welke eindexpressie levert expliciet een ArrayList?Toon antwoordVerberg antwoord
toCollection(ArrayList::new) vraagt om een bekende wijzigbare lijstimplementatie. Stream.toList() is niet-wijzigbaar en Collectors.toList() biedt geen garantie op wijzigbaarheid, ook als een bepaalde implementatie toevallig wijzigingen toestaat. Een verzamelingscollector verandert ook de structuur van het resultaat en de omgang met duplicaten.Vraag 10 van 10
ArrayList<String> words roept forEach(w -> words.add(w + "!")) aan. Hoe kan het programma veilig van elk oorspronkelijk woord één getransformeerde kopie achteraan toevoegen?Toon antwoordVerberg antwoord
ArrayList terwijl de stream die leest, verstoort het doorlopen en kan een ConcurrentModificationException opwerpen. Een andere referentie wijst nog steeds naar dezelfde bron; die scheidt de lees- en schrijfstappen niet.Set 2: Volg typen en resultaten10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Box<Integer> bevat 12. Het programma voert de toewijzing int next = box.get() + 3; uit. Hoe kan dit compileren en 15 opleveren?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
pick hoort bij klasse Items en declareert <T> T pick(List<T> xs). Hoe schrijf je een expliciet typeargument String bij een aanroep?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
T tijdens typewissing vervangen binnen een onbegrensde generieke methode?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
int run(int x), een default-methode reset() en een statische methode help(). Kan x -> x + 2 de interface implementeren?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
String en bepaalt of die eindigt op ".csv". Welke standaardinterface past bij de voorwaarde die filter verwacht?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed(). Welke volgorde van namen ontstaat?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList()?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Stream.<Integer>empty() de resultaten van afzonderlijke aanroepen op nieuwe streams van anyMatch(n -> n > 0), allMatch(n -> n > 0) en noneMatch(n -> n > 0), in die volgorde?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
toMap(Item::getCode, Item::getCount) met twee argumenten komt twee elementen tegen met code "R", beide met aantal 3. Wat gebeurt er?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
partitioningBy(x -> x > 10, Collectors.counting()). Welk resultaat is gegarandeerd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 3: Kies een veilige implementatie10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Pair<String, Integer> p = new Pair<>("zone", 4); het type van p.getFirst() en de waarde van p.getSecond() + 2?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
List<? super Integer> sink. Wat is het meest specifieke type dat voor sink.get(0) is gegarandeerd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
show(Set<Long> xs) als show(Set<String> xs). Waarom mislukt het compileren?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Badge heeft een constructor Badge(String text). Een methode accepteert Function<String, Badge> factory. Welke referentie maakt hetzelfde gedrag als s -> new Badge(s)?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
["z", null, "a"]. De lijst wordt gesorteerd met Comparator.nullsLast(Comparator.reverseOrder()). Welke volgorde ontstaat?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
s wordt gebruikt voor s.count() en daarna voor s.toList(). Hoe moet het programma beide resultaten verkrijgen uit een herbruikbare lijst data?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList()?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 4: Volg de volledige taak10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Slot<T> heeft een statische fabriek nodig die uit elke waarde een getypeerd slot maakt. Welke declaratie vermijdt het gebruik van de klasseparameter vanuit een statische context?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
addAll een willekeurig gekozen elementtype over van een bronlijst naar een wijzigbare doellijst. Ze moet gehele getallen naar een getallenlijst of een objectenlijst kopiëren en de typeveiligheid behouden. Welke signatuur past?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
boolean check(String s) en boolean equals(Object other), zonder andere abstracte methoden. Kan een lambda-expressie die implementeren en waarom?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
String::compareToIgnoreCase wanneer die als Comparator<String> wordt gebruikt?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
(int) (a - b). Wat gebeurt er bij het vergelijken van 3,6 met 3,1?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream)?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
"reed", "oak", "fern", "elm", "cedar". Welke map wordt weergegeven na groeperen op lengte met counting() in een TreeMap?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 5: Pas de ideeën toe op nieuwe taken10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
List<? super Integer> dst, List<? extends Number> src en probeert dst.add(src.get(0)). Ze moet bronnen met gehele getallen of doubles en wijzigbare bestemmingen met getallen of objecten accepteren. Hoe moeten de parameters veranderen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
triple.andThen(shift).apply(4) terug voor Function<Integer, Integer> triple = x -> x * 3; en shift = x -> x + 2;?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
HashMap<String, Integer> bevat oak=6, ash=6, elm=2. Welke comparator voor sleutel-waardeparen garandeert aflopende waarden en bij gelijkheid oplopende sleutels?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
[2, 5], [] en [5, 8]. Wat geeft groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum() terug?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0) terug?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Sale heeft een team en een aantal eenheden als geheel getal. Negeer verkopen met nul of een negatief aantal eenheden, bereken het gemiddelde van de overgebleven verkopen per team en geef het team met het hoogste gemiddelde terug, of “none” bij lege invoer. Neem aan dat geen gemiddelden gelijk zijn. Welke verwerkingspijplijn past? Collectormethoden zijn statisch geïmporteerd.Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
"an", "oak", "to", "elm" de waarde aan de true-kant van partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList()))?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
Aanvullend sorteercriterium
Associativiteit
Autoboxing
5 worden doorgegeven aan een constructor die een Integer verwacht.B
BinaryOperator
BinaryOperator<Integer> zoals Integer::sum kan tellingen voor een herhaalde mapsleutel samenvoegen.Blokbody
return a; en return b; om een maximumbewerking te implementeren.Bovengrens
<T extends Number> staat aanroepen van doubleValue() op een T toe.Bronvolgorde
findFirst de eerste passende naam in een bronlijst.C
Classificatiefunctie
String::length plaatst alle woorden van vier letters onder dezelfde gehele sleutel.Collectiefabriek
ArrayList::new vraagt expliciet om een lijst waaraan later kan worden toegevoegd.Collector
forEach een externe lijst te wijzigen.Comparator
compare een negatief resultaat, nul of een positief resultaat teruggeeft voor twee invoerwaarden.Comparator<String> kan woorden op lengte ordenen zonder hun natuurlijke stringvolgorde te veranderen.Comparator met expliciete null-afhandeling
nullsFirst of nullsLast.Constructorreferentie
ClassName::new om gedrag voor het maken van objecten te leveren, met argumenten die door de doelinterface worden bepaald.Student::new kan met een passende constructor een naam omzetten in een student.Consumer
accept geeft geen waarde terug.Consumer<String> kan elk woord weergeven wanneer die aan forEach van een collectie wordt doorgegeven.D
Diamantoperator
<> in een generieke constructoraanroep en vraagt de compiler typeargumenten af te leiden met behoud van typecontroles.new ArrayList<>() behoudt het lijsttype op basis van de context.distinct
distinct verwijdert dubbele elementen uit een stream op basis van objectgelijkheid en behoudt bij een geordende stream de bronvolgorde.distinct toepast voordat je woorden omzet naar lengtes, kunnen herhaalde lengtes nog steeds in het resultaat voorkomen.Doeltype
Predicate<String> maakt van s -> s.isEmpty() een booleaanse test van een string.Dubbele sleutel
toMap met twee argumenten mislukken, ook als beide bijbehorende waarden 85 zijn.E
Effectief final
final kan vastleggen.Eindbewerking
count geeft een long terug en laat dezelfde stream onbruikbaar achter voor hergebruik.Expliciet methodetypeargument
Lists.<String>last(words) geeft de typekeuze van de methode expliciet aan.Expressiebody
x + 1 geeft x -> x + 1 het verhoogde gehele getal rechtstreeks terug.F
filter
filter behoudt streamelementen waarvoor het predicaat waar is, behoudt hun elementtype en laat andere waarden weg.filter op punten moet vóór de omzetting van studenten naar namen worden uitgevoerd als het de puntengetter nodig heeft.flatMap
flatMap zet elke invoerwaarde om naar een stream en combineert de elementen van die streams tot één uitvoerreeks.flatMap met List::stream zet geneste lijsten met gehele getallen om in één stream met gehele getallen.Functiesamenstelling
andThen en compose geven tegengestelde aanroepvolgorden aan.twice.andThen(inc), wat van 5 de waarde 11 maakt.Function
apply één invoerwaarde van type T transformeert naar een resultaat van type R.Function<String, Integer> kan vóór het groeperen elk woord omzetten naar de lengte ervan.Functionele interface
Object niet meetellen.Operation laat de aanroepende code de berekening met een lambda-expressie opgeven.G
Gebonden methodereferentie
System.out::println geeft uitvoer weer met dat specifieke uitvoerobject.Generieke interface
Container<T> kan de invoer van put verbonden houden met het resultaat van take.Generieke klasse
Box<T> ondersteunt met één declaratie een doos met strings en een doos met gehele getallen.Generieke methode
last geeft een string terug voor een lijst met strings en een geheel getal voor een lijst met gehele getallen.groupingBy
groupingBy bouwt een map van aangetroffen sleutels uit een classificatiefunctie naar lijsten of samenvattingen van de bijpassende elementen door een vervolgcollector.groupingBy-collector met averagingInt slaat per studentengroep één gemiddelde als double-waarde op.I
IntStream
int-waarden met numerieke bewerkingen zoals som en gemiddelde, plus conversies terug naar objecten.IntStream uit mapToInt kan punten optellen zonder eerst wrapperwaarden te verzamelen.Invariantie
List<Integer> als List<Number> af en beschermt de lijst tegen het schrijven van double-waarden.J
joining
joining voegt tekenreeksen samen tot één string, met optionele tekst tussen de elementen en rond het hele resultaat.joining-collector met een komma als scheidingsteken produceert geen komma na de laatste naam.Joker met bovengrens
? extends Number duidt een onbekend type aan dat het opgegeven type of een subtype daarvan is.Joker met ondergrens
? super Integer duidt een onbekend type aan dat het opgegeven type of een supertype daarvan is.List<Number> als bestemming accepteren.L
Lambda-expressie
-> ertussen.(a, b) -> a * b levert de vermenigvuldigingsregel voor een operatie.limit
limit behoudt maximaal het gevraagde aantal elementen vanaf het begin van een geordende stream, zodat latere invoer kan worden overgeslagen.limit vóór het sorteren selecteert uit het oorspronkelijke begin, niet de hoogste waarden van het geheel.Luie evaluatie
findFirst stoppen zodra een passend woord is gevonden en getransformeerd.Lus met index
list.get(i) vergelijken met list.get(i - 1) terwijl twee tellers worden bijgewerkt.M
map
map vervangt elk invoerelement door één berekend resultaat en kan zo het elementtype van de stream veranderen.map met String::length verandert woorden in Integer-wrapperwaarden, niet in een primitieve stream.mapping
mapping transformeert elementen voordat de resultaten worden doorgegeven aan een andere collector, vaak binnen een groep of partitie.mapping-collector kan binnen elke groep studentennamen ophalen voordat een lijst wordt verzameld.Meervoudige typebegrenzing
&, zodat een typeargument aan elke typegrens moet voldoen, met een eventuele klasse voorop.<T extends Number & Comparable<T>> vereist numeriek gedrag en vergelijkingsgedrag.Methodereferentie
:: en past bij de invoer en het resultaat van een functionele interface als doeltype.Integer::parseInt levert parsegedrag aan een functie van string naar geheel getal.N
Natuurlijke volgorde
Comparable.compareTo definieert en die wordt gebruikt wanneer geen afzonderlijke comparator is opgegeven."Cherry" met een hoofdletter vóór "apple" met een kleine letter.Neutraal element
Neveneffect
Niet-verstoring van de bron
ArrayList.Niet-wijzigbare lijst
Stream.toList() werpt een exceptie op wanneer code een ander element probeert toe te voegen.O
Omkering van een comparator
reversed() vertegenwoordigt, inclusief bestaande aanvullende sorteercriteria.Onbegrensde joker
? zonder typegrens en vertegenwoordigt een onbekend argument, waarbij elementen alleen als Object kunnen worden gelezen.List<?> kan een weergavemethode de lijstgrootte bekijken zonder het elementtype te kennen.Ongebonden methodereferentie
String::length roept length() aan op elke opgegeven string.Optional
Optional uit een gefilterd maximum kan leeg zijn, zodat een terugvalwaarde het ontbrekende geval afhandelt.OptionalDouble
OptionalDouble uit average() kan via orElse een gekozen terugvalwaarde van 0,0 leveren.Overloop bij gehele getallen
P
partitioningBy
partitioningBy verdeelt elementen met een predicaat en geeft altijd beide booleaanse sleutels terug, ook als één kant leeg is.partitioningBy-collector kan een lege lijst onder true teruggeven wanneer geen enkele student de drempelwaarde van het predicaat haalt.peek
peek geeft elementen ongewijzigd door en voert een inspectieactie uit voor doorlopen waarden; aanroepen kunnen worden overgeslagen.peek die waarden weergeeft, is onbetrouwbaar voor noodzakelijk werk omdat de uitvoering de stap kan overslaan.Predicaattoetsing
anyMatch, maar true voor allMatch.Predicate
test een boolean teruggeeft.Predicate<String> die aan removeIf wordt doorgegeven, wijst passende woorden aan die moeten worden verwijderd.Producent: extends, consument: super
R
Reductie
Ruw type
List xs kan een verkeerde schrijfbewerking toestaan die een List<String> zou afwijzen.S
Samenvoegfunctie
Math::max behoudt de grootste hoeveelheid wanneer een voorraadsleutel twee keer voorkomt.Signatuur na typewissing
show(Set) is gelijk voor pogingen tot overladen met Set<String> en Set<Long>.skip
skip laat het gevraagde aantal eerste elementen van een geordende stream weg en geeft de overige elementen door.skip(2) op het bereik 1 tot en met 10 is de volgende doorgegeven waarde 3.Sleutel-waardepaar
Sleutelextractor
Student::getPoints bouwt met comparingInt een numerieke studentencomparator.Specialisatie voor primitieve typen
ToIntFunction<String> levert lengtes rechtstreeks aan mapToInt.Stabiele sortering
Statische context
<E> in plaats van de T van de klasse te gebruiken.Stream
points kan de waarden tellen die aan een test voldoen en de lijst ongewijzigd laten.Supplier
get zonder argumenten een waarde van het resultaattype levert.Supplier<List<String>> met ArrayList::new kan bij elke aanroep een nieuwe lijst leveren.T
Transitiviteit
Tussenbewerking
filter wacht op een eindbewerking voordat elementen worden getest.Tussenresultaat
Typeafleiding
last(words) een String op wanneer words een lijst met strings is.Typeargument
String geeft een getter van Box<String> tekst terug zonder expliciete typeconversie.Typeparameter
T geeft het veld en de getter van een doos hetzelfde type dat de aanroepende code kiest.Typewissing
Object, waarbij de compiler de nodige expliciete typeconversies invoegt.Box.U
UnaryOperator
UnaryOperator<String> kan via replaceAll van een wijzigbare lijst elk woord omzetten naar hoofdletters.V
Vastleggen van variabelen
limit.Vervolgcollector
counting() verandert een map met groepslijsten in een map met tellingen van het type Long.Verwerkingspijplijn
stream via filter naar count levert het aantal passende waarden als een long-waarde op.Voortijdig stoppen zodra het resultaat vaststaat
anyMatch true teruggeven zodra één element aan het predicaat voldoet.W
Waarschuwing voor niet-gecontroleerde typeveiligheid
add op een ruwe lijst wijst op een mogelijke latere leesbewerking met het verkeerde type.Wrapperklasse
Integer maakt List<Integer> geldig, terwijl List<int> wordt afgewezen.- 1Eerste Java-programma’s
- 2Voorwaarden en lussen in Java
- 3Java-methoden
- 4Lijsten, arrays en strings in Java
- 5Java-klassen en objecten
- 6Objecten, referenties en gelijkheid in Java
- 7HashMaps en collecties in Java
- 8Overerving in Java
- 9Excepties en bestanden in Java
- 10Klassendiagrammen en unittests in Java
- 11Lambda-expressies en streams in Java
Over deze les
Door StudeliFout melden
Maak je eigen Studiemateriaal
Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.