Generieke typen, lambda-expressies en streams in Java

Schrijf Java-klassen en -methoden die met elk type werken, terwijl de compiler elk gebruik controleert. Geef daarna gedrag als waarde door aan methoden met lambda-expressies en methodereferenties. Sorteer objecten volgens elke gewenste regel met comparators en verwerk collecties met de Stream API: filter, transformeer, sorteer, tel, sommeer, groepeer en verzamel gegevens in enkele leesbare stappen. Elk voorbeeld is een klein, volledig stuk code met het exacte resultaat. Veelvoorkomende compilerfouten en uitvoeringsfouten worden onderweg uitgelegd.

BeginnerLes 11 van 11 in Cursus Java programmeren: klassen, overerving, collecties en streams

Wat je leert

  1. Gebruik typeparameters om klassen, interfaces en methoden te schrijven die met veel typen werken, terwijl de compiler elk gebruik controleert.
    • Definieer en gebruik generieke klassen
      Declareer klassen en interfaces met typeparameters, maak objecten met typeargumenten en leg uit welke typeveiligheid dit biedt vergeleken met het opslaan van Object.
    • Schrijf generieke methoden
      Declareer 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 jokers
      Gebruik begrensde typeparameters om methoden op een generiek type aan te roepen, leg uit waarom een List<Integer> geen List<Number> is en kies tussen ? extends en ? super.
    • Werk binnen de beperkingen van typewissing
      Leg uit hoe de compiler typeargumenten wist, welke bewerkingen generieke typen daardoor niet toestaan en waarom je ruwe typen beter kunt vermijden.
  2. 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 interfaces
      Herken 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 interfaces
      Kies 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 methodereferenties
      Gebruik 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.
  3. 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 daarmee
      Implementeer het contract van compare correct, sorteer lijsten en arrays met een comparator en kies tussen Comparable en Comparator.
    • Combineer comparators met comparing, thenComparing en reversed
      Bouw comparators op uit sorteersleutels, voeg aanvullende sorteercriteria toe, keer volgorden om, handel null-waarden af en sorteer de sleutel-waardeparen van een map.
  4. 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 uitgevoerd
      Maak 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 volgorde
      Gebruik filter, map, mapToInt, flatMap, distinct, sorted, limit en skip en wissel tussen objectstreams en primitieve streams.
    • Reduceer een stream tot één resultaat
      Bereken aantallen, sommen, gemiddelden, extrema en waar-of-onwaarantwoorden met eindbewerkingen en handel ontbrekende resultaten af met Optional.
  5. 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 strings
      Gebruik toList, Collectors.toList, toSet, toCollection, joining en toMap en handel niet-wijzigbare resultaten en dubbele sleutels af.
    • Groepeer en partitioneer gegevens met collectors
      Verdeel gegevens in groepen met groupingBy en partitioningBy en vat elke groep samen met een vervolgcollector.
    • Kies tussen streams en lussen bij een volledige taak
      Los een gegevensverwerkingstaak van begin tot eind op, vergelijk een oplossing met een stream met een lus en houd verwerkingspijplijnen vrij van neveneffecten.

Lambda-expressies en streams in Java: uitleg

5 hoofdstukken

1

Schrijf 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 Integer als 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.

ParameterVeilige 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 T om elementen als T te lezen.
  • Gebruik ? super T om waarden van het type T toe te voegen; gelezen waarden hebben het type Object.
  • Een joker beperkt bewerkingen via een referentie; hij maakt het object niet onveranderlijk.
  • Gebruik een benoemde typeparameter om invoer- en resultaattypen te verbinden.
  • Collections.copy vervangt 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 Object als 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.
2

Geef 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 return nodig.
  • @FunctionalInterface controleert 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
InterfaceFunctionele methodeTaak
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.
  • andThen voert eerst de ontvanger uit; compose voert eerst zijn argument uit.
  • removeIf test een predicaat; replaceAll past een unaire operator toe.
  • merge combineert een bestaande mapwaarde met een opgegeven waarde.
  • computeIfAbsent kan 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.

SoortReferentieEquivalente lambda-expressie
Statische methodeInteger::parseInts -> Integer.parseInt(s)
Gebonden instantiemethodeSystem.out::printlnx -> System.out.println(x)
Ongebonden instantiemethodeString::lengths -> s.length()
ConstructorArrayList::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.
3

Orden 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.
  • Comparable levert 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

  • comparing haalt een sleutel op; primitieve opbouwmethoden vermijden boxing van numerieke sleutels.
  • thenComparing wordt 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.
4

Verwerk 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.
  • sorted moet 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

  • filter behoudt het elementtype; map kan het veranderen.
  • Primitieve streams bieden numerieke bewerkingen die ontbreken in Stream<Integer>.
  • De volgorde van skip, limit en sorteren verandert de geselecteerde elementen.
  • distinct volgt gelijkheid; sorteren van een geordende stream behoudt de volgorde bij een gelijke sorteerrang.
  • flatMap maakt streams die uit de invoerelementen zijn geproduceerd vlak.
  • peek is 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 allMatch en noneMatch waar; anyMatch is onwaar.
  • Een reductie vereist een neutraal element en een associatieve accumulator.
  • Zet om naar long voordat je waarden optelt waarvan het totaal buiten het bereik van int valt.
5

Verzamel 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 toCollection wanneer een specifiek collectietype nodig is.
  • joining verzamelt tekst met een optioneel scheidingsteken, voorvoegsel en achtervoegsel.
  • toMap met 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:

VervolgcollectorWaarde onder AWaardetype
counting()3Long
summingInt(Student::getPoints)234Integer
averagingInt(Student::getPoints)78,0Double
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.
  • counting produceert Long; collectors die gemiddelden berekenen, produceren Double.
  • Een partitie bevat altijd beide booleaanse sleutels.
  • Een ontbrekende groepssleutel geeft null terug, 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

5 Walkthroughs

1

Bouw een typeveilige herbruikbare API

17 stappen
Volg een getypeerde doos, een generieke methode en het overzetten van een lijst om te zien wat Java vóór de uitvoering kan controleren.
  1. 1
    IntroductieBehoud 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.

  2. 2
    UitlegDeclareer de doos

    T benoemt 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.

  3. 3
    VoorbeeldGebruik één concreet type

    Maak een doos en lees de waarde ervan:

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

    String levert 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.

  4. 4
    UitdagingVoorspel de afgewezen schrijfbewerking

    De generieke Box<T> heeft void set(T value). Zal b.set("nine") na Box<Integer> b = new Box<>(8); compileren? Leg uit waar het mislukt.

    ---reveal---

    Het mislukt bij het schrijven omdat deze referentie een Integer verwacht, geen String. Er is geen latere leesbewerking nodig om de fout te ontdekken. De literal 8 kan via boxing worden omgezet in Integer; typeargumenten zelf moeten nog steeds referentietypen zijn, dus Box<int> is ongeldig.

  5. 5
    VerbandKies 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 volgende T benoemt het resultaattype. Een aanroep op [4, 8, 15] geeft de Integer 15 terug; een aanroep op ["x", "y"] geeft de string "y" terug. De methode vereist een niet-lege lijst, onafhankelijk van de typecontroles.

  6. 6
    TipSchrijf 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). De T van de klasse hoort bij geparametriseerd gebruik van instanties, terwijl de statische methode bij de gedeelde klasse hoort.

  7. 7
    UitdagingVerbind 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 schrijft static 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 je Object als resultaat schrijft, gaat dat nuttige verband verloren in plaats van dat je het herstelt.

  8. 8
    UitlegMaak vergelijken beschikbaar

    Een onbeperkte T heeft geen bekende bewerking compareTo. Een typegrens kan die garantie bieden:

    <T extends Comparable<T>>
    

    Een methode largest(List<T>) kan dan een T best bijhouden, elke x met best vergelijken en die vervangen wanneer x.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.

  9. 9
    KernpuntBescherm een lijst met gehele getallen

    Een Integer is een Number, maar een List<Integer> is geen List<Number>.

    Als die lijsttoewijzing was toegestaan, zou code die de referentie naar de getallenlijst gebruikt een Double kunnen toevoegen. De oorspronkelijke eigenaar zou die vervolgens als een Integer lezen. Generieke invariantie voorkomt de toewijzing, zodat zowel lezen als schrijven aan de beloften blijft voldoen. ArrayList<Number> wordt geaccepteerd als List<Number> omdat het elementargument nog steeds overeenkomt.

  10. 10
    VoorbeeldZet 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 een List<Integer> als bron kan Java Integer kiezen voor T. Elk bronelement is acceptabel voor de bestemming, zonder te stellen dat beide lijsten identieke elementargumenten hebben.

  11. 11
    UitdagingKies de veilige leesbewerking

    Een parameter heeft het type List<? super Integer>. Kan het eerste element rechtstreeks aan een Integer-variabele worden toegewezen? Welk type is in plaats daarvan gegarandeerd?

    ---reveal---

    Alleen Object is gegarandeerd. De werkelijke lijst kan een List<Number> of List<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.

  12. 12
    UitlegOnderscheid controle van klassen tijdens de uitvoering

    Typewissing vervangt typeparameters door hun typegrenzen en gebruikt Object wanneer 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 een Box<Integer> delen tijdens de uitvoering dezelfde klasse Box. 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.

  13. 13
    VoorbeeldLever 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 zoals ArrayList<T> is meestal geschikt voor de opslag.

  14. 14
    TipVolg de waarschuwing over een ruw type

    Een ruwe List kan "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.

  15. 15
    UitdagingControleer de overladen methode na typewissing

    Eén klasse declareert show(Set<String> xs) en show(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 van Object tegen List<String> heeft een verwante beperking: test List<?> zonder iets over het elementargument te stellen.

  16. 16
    UitdagingBekijk een herbruikbare API voor overdracht

    Een API gebruikt een ruwe bronlijst, zet elk element expliciet om naar Number en 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 als Number worden 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.

  17. 17
    SamenvattingLees 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.

2

Maak van gedrag een argument

14 stappen
Bouw en combineer functionele regels en lees daarna vastgelegde variabelen en methodereferenties correct.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBegin met de vorm van het gedrag
  2. 2
    UitlegZoek de te implementeren methode
  3. 3
    VoorbeeldGebruik een blok als een beslissing dat vereist
  4. 4
    UitdagingHerstel het ontbrekende resultaat
  5. 5
    UitlegKoppel vier veelvoorkomende taken
  6. 6
    VoorbeeldLet op de volgorde van samenstellen
  7. 7
    VerbandLaat collecties de regel uitvoeren
  8. 8
    UitdagingOnderscheid verwijderen van selecteren
  9. 9
    UitlegLeg een waarde vast die niet opnieuw wordt toegewezen
  10. 10
    VoorbeeldLees de ontvanger uit de referentie af
  11. 11
    UitdagingControleer wat final beschermt
  12. 12
    VoorbeeldTel met een bijwerkregel voor een map
  13. 13
    UitdagingCombineer doeltypering en vastleggen
  14. 14
    SamenvattingVolg één aanroep door de regel
3

Bouw een ordening stap voor stap op

12 stappen
Volg vergelijkingstekens, gelijke sorteerrangen en omkeringen voordat je een comparator op lijsten of sleutel-waardeparen gebruikt.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBenoem de volgorde die de taak nodig heeft
  2. 2
    UitlegLees een vergelijkingsresultaat
  3. 3
    VoorbeeldVermijd aftrekken bij numerieke vergelijkingen
  4. 4
    VoorbeeldBehoud bij gelijke scores de bronvolgorde
  5. 5
    UitdagingOnderscheid natuurlijke volgorde van taakgerichte volgorde
  6. 6
    UitlegVoeg alleen bij gelijkheid een regel toe
  7. 7
    KernpuntKeer alleen de bedoelde beslissingen om
  8. 8
    TipHandel null-waarden op het juiste niveau af
  9. 9
    UitdagingLees de volledige omkering
  10. 10
    VerbandOrden de sleutel-waardeparen, niet de map
  11. 11
    UitdagingMaak de volgorde van gelijke mapwaarden voorspelbaar
  12. 12
    SamenvattingControleer de vergelijkingsboom
4

Volg waarden door een stream

16 stappen
Volg de waarden en typen in een verwerkingspijplijn, kies daarna een eindresultaat en handel ontbrekende waarden af.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVolg waarden en typen samen
  2. 2
    UitlegBepaal het eindresultaat
  3. 3
    VoorbeeldStop na de eerste passende waarde
  4. 4
    UitdagingVoer een andere berekening uit
  5. 5
    VoorbeeldSelecteer studenten vóór het omzetten naar namen
  6. 6
    VerbandGa over naar numerieke bewerkingen
  7. 7
    VoorbeeldMaak vlak voordat je duplicaten verwijdert
  8. 8
    UitdagingLees de volgorde van bewerkingen
  9. 9
    VoorbeeldBereken een som en een gemiddelde
  10. 10
    UitlegHoud afwezigheid gescheiden van een waarde
  11. 11
    UitdagingHandel een ontbrekend maximum af
  12. 12
    KernpuntControleer algemene tests bij lege invoer
  13. 13
    VoorbeeldKies een neutrale beginwaarde
  14. 14
    TipVerbreed voordat overloop optreedt
  15. 15
    UitdagingCombineer vlak maken, selectie en een resultaat
  16. 16
    SamenvattingControleer de randgevallen van het resultaat
5

Bouw een bruikbare gegevenssamenvatting

15 stappen
Kies resultaatcontainers, los dubbele sleutels op en bevraag samenvattingen per groep zonder verborgen bijwerkingen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieWerk terug vanuit het gewenste resultaat
  2. 2
    UitlegMaak latere wijzigingen tot een expliciete eis
  3. 3
    VoorbeeldLos botsingen op met een samenvoegregel
  4. 4
    UitdagingKies één waarde per herhaalde sleutel
  5. 5
    VerbandVoeg waarden samen als tekst
  6. 6
    UitlegKies een sleutel en daarna een groepsresultaat
  7. 7
    VoorbeeldVat gegevens binnen de studentengroepen samen
  8. 8
    KernpuntBehoud beide kanten van een partitie
  9. 9
    UitdagingVoorspel een lege kant
  10. 10
    VoorbeeldBereken eerst stadsgemiddelden
  11. 11
    VoorbeeldBevraag de samenvatting die je hebt opgebouwd
  12. 12
    TipMaak de besturingsstroom zichtbaar als dat helpt
  13. 13
    UitdagingBescherm de bron tijdens de verwerking
  14. 14
    UitdagingZoek de winnende groep veilig
  15. 15
    SamenvattingBekijk de hele gegevenstaak

Lambda-expressies en streams in Java: oefenvragen

50 Oefenvragen in 5 sets

Set 1: Gebruik de basis10 Oefenvragen

Vraag 1 van 10

Een generieke 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()?
  1. Object
  2. String
  3. Box<String>
  4. CharSequence
Toon antwoordVerberg antwoord
Antwoord: B
Het typeargument 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

Een gewone hulpklasse heeft een statische methode nodig die het eerste element van elke niet-lege lijst teruggeeft en het elementtype behoudt. Welke declaratie past?
  1. static E first(List<E> xs)
  2. static <E> Object first(List<Object> xs)
  3. static <E> List<E> first(E xs)
  4. static <E> E first(List<E> xs)
Toon antwoordVerberg antwoord
Antwoord: D
De methode introduceert 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

Een methode leest een lijst met getallen alleen om op elk element doubleValue() aan te roepen. Ze moet zowel List<Integer> als List<Double> accepteren. Welk parametertype past?
  1. List<? extends Number>
  2. List<Number>
  3. List<? super Number>
  4. List<Object>
Toon antwoordVerberg antwoord
Antwoord: A
Een extends-joker accepteert een onbekend subtype van 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

Een Function<Integer, Integer> moet zijn invoer verdubbelen met een blokbody. Welke lambda-expressie past?
  1. x -> { x * 2; }
  2. x -> { return; }
  3. x -> { return x * 2; }
  4. () -> { return x * 2; }
Toon antwoordVerberg antwoord
Antwoord: C
Een blok dat een waarde teruggeeft, moet die expliciet teruggeven. 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

Een predicaat moet elke geteste string registreren en de oorspronkelijke drempelwaarde gebruiken, terwijl een afzonderlijke teller met de naam limit mag blijven veranderen. De code legt beide lokale variabelen vast: 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?
  1. Maak de lijstreferentie final; blijf de veranderende limit vastleggen.
  2. Verplaats de verhoging vóór de lambda-expressie; blijf dezelfde limit vastleggen.
  3. Leg een afzonderlijke vaste drempelwaarde vast; behoud dezelfde lijstreferentie.
  4. Vervang de lijst door een niet-wijzigbare lijst; blijf de veranderende limit vastleggen.
Toon antwoordVerberg antwoord
Antwoord: C
Door de verhoging kan limit niet worden vastgelegd. Kopieer daarom de beginwaarde naar een afzonderlijke effectief final lokale variabele en leg die drempelwaarde vast. De ongewijzigde referentie notes mag nog steeds verwijzen naar een lijst waaraan registraties worden toegevoegd. Als je de verhoging naar voren verplaatst, is de variabele na hertoewijzing nog steeds niet effectief final.

Vraag 6 van 10

Wat geeft een negatief resultaat van compare(a, b) aan bij een oplopende volgorde?
  1. a hoort vóór b.
  2. a hoort na b.
  3. a en b zijn gelijke objecten.
  4. a is precies één positie kleiner.
Toon antwoordVerberg antwoord
Antwoord: A
Het teken van de vergelijking bepaalt de onderlinge positie: een negatief resultaat plaatst het eerste argument vóór het tweede. Nul duidt op een gelijke sorteerrang, terwijl een positief resultaat het eerste argument na het tweede plaatst. De grootte van het negatieve resultaat geeft geen afstand of aantal posities weer.

Vraag 7 van 10

Een programma bouwt names.stream().filter(s -> s.length() > 2) op en doet niets met de teruggegeven stream. Wanneer wordt het predicaat aangeroepen?
  1. Alle aanroepen gebeuren tijdens de aanroep van stream().
  2. Alle aanroepen gebeuren tijdens de aanroep van filter().
  3. De aanroepen gebeuren wanneer de bronlijst opnieuw wordt gelezen.
  4. De aanroepen gebeuren niet zonder een eindbewerking.
Toon antwoordVerberg antwoord
Antwoord: D
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

Een niet-lege stream bevat 2, 4 en 6. Een product wordt berekend met reduce(0, (a, b) -> a * b). Wat veroorzaakt het verkeerde resultaat?
  1. De accumulator moet één argument nemen in plaats van twee.
  2. Nul is niet het neutrale element voor vermenigvuldigen en houdt de accumulator op nul.
  3. Een stream met gehele getallen kan niet met vermenigvuldigen worden gereduceerd.
  4. De eerste invoerwaarde moet altijd als expliciet neutraal element dienen.
Toon antwoordVerberg antwoord
Antwoord: B
Als je de beginnul met 2 vermenigvuldigt, krijg je nul en elke latere vermenigvuldiging blijft nul. Gebruik 1 als neutraal element, zodat de accumulator de eerste waarde behoudt en daarna het product 48 opbouwt. Een neutraal element wordt gekozen voor de bewerking, niet willekeurig uit de invoerwaarden genomen.

Vraag 9 van 10

Een resultaatlijst moet een latere add ondersteunen. Welke eindexpressie levert expliciet een ArrayList?
  1. toList()
  2. collect(Collectors.toList())
  3. collect(Collectors.toCollection(ArrayList::new))
  4. collect(Collectors.toSet())
Toon antwoordVerberg antwoord
Antwoord: C
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

Een verwerkingspijplijn op een ArrayList<String> words roept forEach(w -> words.add(w + "!")) aan. Hoe kan het programma veilig van elk oorspronkelijk woord één getransformeerde kopie achteraan toevoegen?
  1. Verzamel de kopieën afzonderlijk en roep daarna words.addAll(copies) aan.
  2. Voeg peek(w -> words.add(w + "!")) toe vóór forEach.
  3. Vervang forEach door map en behoud daarbinnen words.add.
  4. Bewaar words in een andere variabele en voeg toe via die referentie.
Toon antwoordVerberg antwoord
Antwoord: A
Door de kopieën eerst te verzamelen kan het doorlopen worden voltooid voordat de oorspronkelijke lijst verandert. Toevoegen aan een 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

Een Box<Integer> bevat 12. Het programma voert de toewijzing int next = box.get() + 3; uit. Hoe kan dit compileren en 15 opleveren?
  1. De getter geeft int terug, omdat generieke argumenten primitief zijn.
  2. De getter geeft Object terug, dat bij het rekenen automatisch wordt omgezet.
  3. De getter geeft Integer terug, dat bij het rekenen via unboxing wordt omgezet.
  4. De getter geeft Double terug, dat bij het rekenen automatisch wordt versmald.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een statische methode pick hoort bij klasse Items en declareert <T> T pick(List<T> xs). Hoe schrijf je een expliciet typeargument String bij een aanroep?
  1. Items.<String>pick(words)
  2. Items.pick<String>(words)
  3. <String>Items.pick(words)
  4. Items.String.pick(words)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Waardoor wordt T tijdens typewissing vervangen binnen een onbegrensde generieke methode?
  1. String
  2. Object
  3. Class<T>
  4. Number
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een interface declareert int run(int x), een default-methode reset() en een statische methode help(). Kan x -> x + 2 de interface implementeren?
  1. Nee; drie gedeclareerde methoden vereisen drie lambdabodies.
  2. Ja; een lambda-expressie levert ook default- en statische bodies.
  3. Nee; een functionele methode moet apply heten.
  4. Ja; alleen run vereist een abstracte implementatie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een herbruikbare regel neemt een String en bepaalt of die eindigt op ".csv". Welke standaardinterface past bij de voorwaarde die filter verwacht?
  1. Predicate<String>
  2. Consumer<String>
  3. Supplier<Boolean>
  4. Function<String, Boolean>
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Elementen zijn Mira (groep A, 7), Niko (B, 9), Oren (A, 4), Pia (B, 6). Ze worden gesorteerd met comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed(). Welke volgorde van namen ontstaat?
  1. [Pia, Niko, Oren, Mira]
  2. [Niko, Pia, Mira, Oren]
  3. [Mira, Oren, Niko, Pia]
  4. [Oren, Mira, Pia, Niko]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Welke lijst ontstaat uit IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList()?
  1. [2, 3]
  2. [4, 5]
  3. [5, 6]
  4. [7, 8]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Wat zijn voor 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?
  1. false, false, true
  2. true, true, false
  3. false, false, false
  4. false, true, true
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een toMap(Item::getCode, Item::getCount) met twee argumenten komt twee elementen tegen met code "R", beide met aantal 3. Wat gebeurt er?
  1. De methode slaat R=3 op omdat de herhaalde waarden gelijk zijn.
  2. De methode slaat R=6 op door gelijke waarden automatisch op te tellen.
  3. De methode slaat beide paren op onder twee kopieën van sleutel R.
  4. De methode werpt een IllegalStateException op voor de herhaalde sleutel.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een lege stream wordt verzameld met partitioningBy(x -> x > 10, Collectors.counting()). Welk resultaat is gegarandeerd?
  1. De map is leeg omdat geen classificatiefunctie is uitgevoerd.
  2. Beide booleaanse sleutels zijn gekoppeld aan 0L.
  3. Alleen false is aanwezig en is gekoppeld aan 0L.
  4. Beide booleaanse sleutels zijn gekoppeld aan lege optionals.
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

Een generieke Pair<L, R> heeft getters die L en R teruggeven. Wat zijn voor Pair<String, Integer> p = new Pair<>("zone", 4); het type van p.getFirst() en de waarde van p.getSecond() + 2?
  1. String en 6
  2. Object en 6
  3. String en 42
  4. Integer en 6
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een parameter is gedeclareerd als List<? super Integer> sink. Wat is het meest specifieke type dat voor sink.get(0) is gegarandeerd?
  1. Integer
  2. Number
  3. Object
  4. Comparable<Integer>
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een klasse declareert zowel show(Set<Long> xs) als show(Set<String> xs). Waarom mislukt het compileren?
  1. Elke generieke methode moet haar elementtype teruggeven.
  2. Set staat in een hele klasse maar één typeargument toe.
  3. Overladen methoden in Java kunnen helemaal geen collectieparameters gebruiken.
  4. Beide parametertypen worden door typewissing Set.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een methode moet een opgegeven actie op elke string uitvoeren zonder een resultaat van die actie te ontvangen. Welke parameterinterface past?
  1. Supplier<String>
  2. Consumer<String>
  3. Predicate<String>
  4. UnaryOperator<String>
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een klasse Badge heeft een constructor Badge(String text). Een methode accepteert Function<String, Badge> factory. Welke referentie maakt hetzelfde gedrag als s -> new Badge(s)?
  1. Badge::toString
  2. Badge::new
  3. new Badge::new
  4. Badge::Badge
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een lijst is ["z", null, "a"]. De lijst wordt gesorteerd met Comparator.nullsLast(Comparator.reverseOrder()). Welke volgorde ontstaat?
  1. [null, z, a]
  2. [a, z, null]
  3. [null, a, z]
  4. [z, a, null]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een streamvariabele s wordt gebruikt voor s.count() en daarna voor s.toList(). Hoe moet het programma beide resultaten verkrijgen uit een herbruikbare lijst data?
  1. Start voor elke eindbewerking een nieuwe data.stream().
  2. Roep s.close() aan tussen de eindbewerkingen.
  3. Sla s op in een andere variabele voordat je count() aanroept.
  4. Voeg s.limit(data.size()) in nadat je count() hebt aangeroepen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Welke lijst ontstaat uit Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList()?
  1. [3, 4]
  2. [3, 3, 4, 4]
  3. [3, 3, 4]
  4. [4, 3, 3]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Elementen hebben codes en hoeveelheden P=2, Q=6, P=5, Q=1. Het programma moet een wijzigbare lijst met unieke codes in oplopende volgorde teruggeven. Welke verwerkingspijplijn past?
  1. items.stream().map(Item::getCode).distinct().sorted().toList()
  2. items.stream().map(Item::getCode).distinct().sorted(Comparator.reverseOrder()).collect(Collectors.toCollection(ArrayList::new))
  3. items.stream().map(Item::getCode).distinct().sorted().collect(Collectors.toCollection(ArrayList::new))
  4. items.stream().map(Item::getCode).sorted().collect(Collectors.toCollection(ArrayList::new))
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een taak vergelijkt elk lijstelement met het element direct ervoor en werkt onder verschillende voorwaarden twee tellers bij. Waarom is een lus met index een redelijke keuze?
  1. Die maakt naast elkaar liggende indexen en afgestemde tellerupdates zichtbaar.
  2. Die is vereist omdat streams geen naast elkaar liggende lijstposities kunnen bekijken.
  3. Die garandeert snellere verwerking dan een stream over dezelfde lijst.
  4. Die staat toevoegingen aan de bron toe binnen een gewone for-each-lus.
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

Een generieke klasse 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?
  1. static Slot<T> of(T value)
  2. static <E> Slot<T> of(E value)
  3. static Slot<Object> of(T value)
  4. static <E> Slot<E> of(E value)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een methode zet met 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?
  1. <T> void append(List<? extends T> dst, List<? super T> src)
  2. <T> void append(List<T> dst, List<T> src)
  3. <T> void append(List<? super T> dst, List<? extends T> src)
  4. <T> void append(List<Object> dst, List<T> src)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een interface declareert boolean check(String s) en boolean equals(Object other), zonder andere abstracte methoden. Kan een lambda-expressie die implementeren en waarom?
  1. Ja; de overeenkomst met een Object-methode voegt geen te implementeren abstracte methode toe.
  2. Nee; elke gedeclareerde abstracte methode telt mee, dus de interface heeft er twee.
  3. Ja; Java levert altijd één ontbrekende methode voor elke interface met twee methoden.
  4. Nee; een functionele methode die een booleaanse waarde teruggeeft, moet test heten.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Welke lambda-expressie heeft hetzelfde gedrag als de ongebonden referentie String::compareToIgnoreCase wanneer die als Comparator<String> wordt gebruikt?
  1. (a, b) -> b.compareToIgnoreCase(a)
  2. (a, b) -> a.compareToIgnoreCase(b)
  3. a -> a.compareToIgnoreCase(a)
  4. (a, b) -> String.compareToIgnoreCase(a, b)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een wijzigbare lijst bevat X met score 8, Y met score 3, Z met score 8 en W met score 3, in die volgorde. De lijst wordt met een geldige comparator alleen op oplopende score gesorteerd. Welke volgorde van namen is gegarandeerd?
  1. [Y, W, X, Z]
  2. [W, Y, Z, X]
  3. [Y, W, Z, X]
  4. [X, Z, Y, W]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een comparator voor metingen met double-waarden gebruikt (int) (a - b). Wat gebeurt er bij het vergelijken van 3,6 met 3,1?
  1. De comparator geeft correct aan dat de eerste waarde groter is.
  2. De comparator geeft correct aan dat de eerste waarde kleiner is.
  3. De comparator geeft gelijkheid aan, hoewel de eerste waarde groter is.
  4. De comparator werpt een exceptie op bij een resultaat met decimalen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Wat is het resultaattype van List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream)?
  1. Stream<String>
  2. Stream<Stream<String>>
  3. Stream<List<String>>
  4. List<Stream<String>>
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een methode geeft het grootste gehele getal in een willekeurige lijst terug, of -1 als de lijst leeg is. Welke expressie voldoet aan die eis?
  1. xs.stream().reduce(-1, Integer::max)
  2. xs.stream().max(Integer::compare).get()
  3. xs.stream().findFirst().orElse(-1)
  4. xs.stream().max(Integer::compare).orElse(-1)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

De woorden zijn "reed", "oak", "fern", "elm", "cedar". Welke map wordt weergegeven na groeperen op lengte met counting() in een TreeMap?
  1. {3=1, 4=2, 5=2}
  2. {3=2, 4=1, 5=1}
  3. {3=2, 4=2, 5=1}
  4. {2=3, 1=5}
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

De metingen zijn Delta 6 °C, Delta 18 °C, Cove 14 °C en Cove 16 °C. De taak vraagt om steden met een gemiddelde boven 13 °C. Welk resultaat is correct?
  1. Alleen Cove voldoet.
  2. Cove en Delta voldoen.
  3. Alleen Delta voldoet.
  4. Geen enkele stad voldoet.
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

Een methode heeft de parameters 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?
  1. List<? super Integer> dst, List<? extends Integer> src
  2. List<? super Number> dst, List<? extends Number> src
  3. List<Number> dst, List<? extends Number> src
  4. List<? extends Number> dst, List<? super Number> src
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een generieke fabriek moet een nieuwe waarde maken van het referentietype dat de aanroepende code kiest, zonder aan te nemen dat er een constructor zonder parameters bestaat. Welk ontwerp past?
  1. Gebruik new T() binnen een methode die <T> declareert.
  2. Gebruik new Object() en geef dit rechtstreeks terug als T.
  3. Accepteer Supplier<T> en roep de methode get() aan.
  4. Gebruik T.class om de constructor van de aanroepende code te vinden.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Wat geeft triple.andThen(shift).apply(4) terug voor Function<Integer, Integer> triple = x -> x * 3; en shift = x -> x + 2;?
  1. 18
  2. 12
  3. 20
  4. 14
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een HashMap<String, Integer> bevat oak=6, ash=6, elm=2. Welke comparator voor sleutel-waardeparen garandeert aflopende waarden en bij gelijkheid oplopende sleutels?
  1. Map.Entry.<String, Integer>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey())
  2. Map.Entry.<String, Integer>comparingByValue().thenComparing(Map.Entry.comparingByKey()).reversed()
  3. Map.Entry.<String, Integer>comparingByKey().thenComparing(Map.Entry.comparingByValue())
  4. Map.Entry.<String, Integer>comparingByValue().reversed()
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Tickets zijn Ivo (2, 5), June (1, 7), Kai (1, 4), Luz (2, 5), waarbij elk paar de groep en score aangeeft. Welke volgorde ontstaat bij oplopende groep, aflopende score en daarna oplopende naam?
  1. [Kai, June, Ivo, Luz]
  2. [Ivo, Luz, June, Kai]
  3. [June, Kai, Ivo, Luz]
  4. [June, Kai, Luz, Ivo]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Wat levert dit op en welke woorden worden door het filter getest? Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()
  1. Optional[5]; ash en cedar worden getest.
  2. Optional[4]; alle vier woorden worden getest.
  3. Optional[5]; alle vier woorden worden getest.
  4. Optional[3]; alleen ash wordt getest.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een lijst heeft de geneste groepen [2, 5], [] en [5, 8]. Wat geeft groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum() terug?
  1. 18
  2. 15
  3. 20
  4. 13
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

De waarden zijn -9, -4 en -12. Wat geeft values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0) terug?
  1. 0
  2. -8
  3. -4
  4. -18
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een 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.
  1. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, summingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  2. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  3. sales.stream().collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().filter(e -> e.getValue() > 0).max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  4. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByKey()).map(Map.Entry::getKey).orElse("none")
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Wat is voor de woorden "an", "oak", "to", "elm" de waarde aan de true-kant van partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList()))?
  1. [AN, TO]
  2. [OAK, ELM]
  3. [AN, TO, ELM]
  4. [AN, OAK, TO, ELM]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

87 Kernbegrippen, elk met een definitie

A

Aanvullend sorteercriterium
Een aanvullend sorteercriterium is een extra vergelijkingsregel die alleen wordt gebruikt wanneer alle voorgaande regels de twee elementen dezelfde sorteerrang geven.
Een aanvullend sorteercriterium op naam zet Ada vóór Chen nadat hun gelijke scores de eerste vergelijking gelijk laten eindigen.
Associativiteit
Associativiteit betekent dat het opnieuw groeperen van de invoerwaarden van een bewerking het resultaat niet verandert, zoals vereist voor de accumulator van een streamreductie.
Door associativiteit is optellen geschikt voor een reductie met gehele getallen, terwijl aftrekken ongeschikt is.
Autoboxing
Autoboxing is de automatische omzetting door Java van een primitieve waarde naar het bijbehorende wrapperobject wanneer een referentiewaarde nodig is.
Door autoboxing kan de literal 5 worden doorgegeven aan een constructor die een Integer verwacht.

B

BinaryOperator
Een BinaryOperator is een functie die twee waarden van hetzelfde type combineert tot één resultaat van datzelfde type.
Een BinaryOperator<Integer> zoals Integer::sum kan tellingen voor een herhaalde mapsleutel samenvoegen.
Blokbody
Een blokbody is een lambdabody tussen accolades; wanneer een resultaat vereist is, moeten de paden expliciet waarden teruggeven.
Een blokbody gebruikt return a; en return b; om een maximumbewerking te implementeren.
Bovengrens
Een bovengrens beperkt een typeparameter tot een genoemd type en de subtypen daarvan en maakt de bewerkingen van dat type beschikbaar in generieke code.
De bovengrens in <T extends Number> staat aanroepen van doubleValue() op een T toe.
Bronvolgorde
De bronvolgorde is de vastgelegde volgorde waarin een bron haar elementen aanbiedt, zoals de lijstvolgorde of de sleutelvolgorde van een gesorteerde map.
Door de bronvolgorde kiest findFirst de eerste passende naam in een bronlijst.

C

Classificatiefunctie
Een classificatiefunctie wijst elk invoerelement een groepssleutel toe en bepaalt zo welk sleutel-waardepaar het element ontvangt.
De classificatiefunctie String::length plaatst alle woorden van vier letters onder dezelfde gehele sleutel.
Collectiefabriek
Een collectiefabriek levert een specifieke collectieimplementatie aan een collector, zodat het resultaat het benodigde gedrag of de benodigde ordening heeft.
De collectiefabriek ArrayList::new vraagt expliciet om een lijst waaraan later kan worden toegevoegd.
Collector
Een collector beschrijft hoe de elementen van een stream worden verzameld of samengevat tot een resultaat, zoals een lijst, string, map of gegroepeerde berekening.
Een collector kan de resultaatlijst rechtstreeks opbouwen in plaats van binnen forEach een externe lijst te wijzigen.
Comparator
Een Comparator is een afzonderlijke ordeningsregel waarvan de methode compare een negatief resultaat, nul of een positief resultaat teruggeeft voor twee invoerwaarden.
Een Comparator<String> kan woorden op lengte ordenen zonder hun natuurlijke stringvolgorde te veranderen.
Comparator met expliciete null-afhandeling
Een comparator met expliciete null-afhandeling geeft null-waarden een vaste positie, meestal door een volgorde voor waarden die niet null zijn te omhullen met nullsFirst of nullsLast.
Een comparator met expliciete null-afhandeling voor namen verwerkt een bestaande student van wie de opgehaalde naam null is.
Constructorreferentie
Een constructorreferentie gebruikt ClassName::new om gedrag voor het maken van objecten te leveren, met argumenten die door de doelinterface worden bepaald.
De constructorreferentie Student::new kan met een passende constructor een naam omzetten in een student.
Consumer
Een Consumer is een standaard functionele interface voor een actie op één invoerwaarde; de methode accept geeft geen waarde terug.
Een Consumer<String> kan elk woord weergeven wanneer die aan forEach van een collectie wordt doorgegeven.

D

Diamantoperator
De diamantoperator is <> in een generieke constructoraanroep en vraagt de compiler typeargumenten af te leiden met behoud van typecontroles.
De diamantoperator in new ArrayList<>() behoudt het lijsttype op basis van de context.
distinct
De bewerking distinct verwijdert dubbele elementen uit een stream op basis van objectgelijkheid en behoudt bij een geordende stream de bronvolgorde.
Als je distinct toepast voordat je woorden omzet naar lengtes, kunnen herhaalde lengtes nog steeds in het resultaat voorkomen.
Doeltype
Een doeltype is de verwachte functionele interface die een lambda-expressie of methodereferentie haar parameter- en resultaattypen geeft.
Het doeltype Predicate<String> maakt van s -> s.isEmpty() een booleaanse test van een string.
Dubbele sleutel
Een dubbele sleutel ontstaat wanneer verschillende invoerelementen gelijke mapsleutels opleveren, zodat een regel nodig is om één opgeslagen waarde te bepalen.
Een dubbele sleutel laat toMap met twee argumenten mislukken, ook als beide bijbehorende waarden 85 zijn.

E

Effectief final
Een effectief final lokale variabele wordt eenmaal toegewezen zonder latere hertoewijzing, zodat een lambda-expressie die zonder het trefwoord final kan vastleggen.
Een effectief final lijstreferentie kan nog steeds verwijzen naar een lijst waaraan elementen worden toegevoegd.
Eindbewerking
Een eindbewerking beëindigt een verwerkingspijplijn met streams door een resultaat te produceren of een actie uit te voeren en verbruikt de stream voor die berekening.
De eindbewerking count geeft een long terug en laat dezelfde stream onbruikbaar achter voor hergebruik.
Expliciet methodetypeargument
Een expliciet methodetypeargument is een typeargument dat bij een aanroep tussen de punt na de ontvanger en de methodenaam wordt geschreven.
Het expliciete methodetypeargument in Lists.<String>last(words) geeft de typekeuze van de methode expliciet aan.
Expressiebody
Een expressiebody is een lambdabody die als één expressie is geschreven en waarvan de waarde het vereiste resultaat levert zonder expliciete return-instructie.
Door de expressiebody x + 1 geeft x -> x + 1 het verhoogde gehele getal rechtstreeks terug.

F

filter
De bewerking filter behoudt streamelementen waarvoor het predicaat waar is, behoudt hun elementtype en laat andere waarden weg.
Een filter op punten moet vóór de omzetting van studenten naar namen worden uitgevoerd als het de puntengetter nodig heeft.
flatMap
De bewerking flatMap zet elke invoerwaarde om naar een stream en combineert de elementen van die streams tot één uitvoerreeks.
Een flatMap met List::stream zet geneste lijsten met gehele getallen om in één stream met gehele getallen.
Functiesamenstelling
Functiesamenstelling verbindt transformaties zodat de ene het resultaat van de andere ontvangt; andThen en compose geven tegengestelde aanroepvolgorden aan.
Functiesamenstelling verdubbelt en verhoogt daarna in twice.andThen(inc), wat van 5 de waarde 11 maakt.
Function
Een Function is een standaard functionele interface die via apply één invoerwaarde van type T transformeert naar een resultaat van type R.
Een Function<String, Integer> kan vóór het groeperen elk woord omzetten naar de lengte ervan.
Functionele interface
Een functionele interface heeft één te implementeren abstracte methode, waarbij declaraties die overeenkomen met publieke instantiemethoden van Object niet meetellen.
Een functionele interface zoals Operation laat de aanroepende code de berekening met een lambda-expressie opgeven.

G

Gebonden methodereferentie
Een gebonden methodereferentie benoemt een instantiemethode op een bepaald ontvangend object dat vastligt wanneer de referentie wordt geëvalueerd.
De gebonden methodereferentie System.out::println geeft uitvoer weer met dat specifieke uitvoerobject.
Generieke interface
Een generieke interface declareert typeparameters die de typen in haar methodecontract tussen verschillende implementaties verbinden.
Een generieke interface Container<T> kan de invoer van put verbonden houden met het resultaat van take.
Generieke klasse
Een generieke klasse declareert één of meer typeparameters zodat haar velden en methoden typeverbanden kunnen behouden die de aanroepende code kiest.
De generieke klasse Box<T> ondersteunt met één declaratie een doos met strings en een doos met gehele getallen.
Generieke methode
Een generieke methode declareert haar eigen typeparameters vóór haar retourtype, zodat elke aanroep samenhangende invoer- en resultaattypen kan kiezen.
De generieke methode last geeft een string terug voor een lijst met strings en een geheel getal voor een lijst met gehele getallen.
groupingBy
De collector groupingBy bouwt een map van aangetroffen sleutels uit een classificatiefunctie naar lijsten of samenvattingen van de bijpassende elementen door een vervolgcollector.
Een groupingBy-collector met averagingInt slaat per studentengroep één gemiddelde als double-waarde op.

I

IntStream
Een IntStream is een stream met primitieve int-waarden met numerieke bewerkingen zoals som en gemiddelde, plus conversies terug naar objecten.
Een IntStream uit mapToInt kan punten optellen zonder eerst wrapperwaarden te verzamelen.
Invariantie
Invariantie betekent dat toepassingen van een generiek type met verschillende argumenten geen subtyperelatie krijgen alleen omdat hun argumenttypen die hebben.
Invariantie wijst het gebruik van List<Integer> als List<Number> af en beschermt de lijst tegen het schrijven van double-waarden.

J

joining
De collector joining voegt tekenreeksen samen tot één string, met optionele tekst tussen de elementen en rond het hele resultaat.
Een joining-collector met een komma als scheidingsteken produceert geen komma na de laatste naam.
Joker met bovengrens
Een joker met bovengrens zoals ? extends Number duidt een onbekend type aan dat het opgegeven type of een subtype daarvan is.
Met een joker met bovengrens kan een methode voor een numeriek totaal zowel lijsten met gehele getallen als lijsten met doubles lezen.
Joker met ondergrens
Een joker met ondergrens zoals ? super Integer duidt een onbekend type aan dat het opgegeven type of een supertype daarvan is.
Met een joker met ondergrens kan code die gehele getallen schrijft een wijzigbare List<Number> als bestemming accepteren.

L

Lambda-expressie
Een lambda-expressie levert parameters en een body voor de abstracte methode van een functionele interface, met de pijl -> ertussen.
De lambda-expressie (a, b) -> a * b levert de vermenigvuldigingsregel voor een operatie.
limit
De bewerking limit behoudt maximaal het gevraagde aantal elementen vanaf het begin van een geordende stream, zodat latere invoer kan worden overgeslagen.
Een limit vóór het sorteren selecteert uit het oorspronkelijke begin, niet de hoogste waarden van het geheel.
Luie evaluatie
Luie evaluatie stelt verwerking uit totdat een resultaat die vereist en maakt het mogelijk dat een stream alleen zoveel elementen verwerkt als nodig zijn.
Dankzij luie evaluatie kan findFirst stoppen zodra een passend woord is gevonden en getransformeerd.
Lus met index
Een lus met index gebruikt een expliciete positievariabele om elementen van een reeks te benaderen en maakt naast elkaar liggende posities en besturingsbeslissingen zichtbaar.
Een lus met index kan list.get(i) vergelijken met list.get(i - 1) terwijl twee tellers worden bijgewerkt.

M

map
De bewerking map vervangt elk invoerelement door één berekend resultaat en kan zo het elementtype van de stream veranderen.
Een map met String::length verandert woorden in Integer-wrapperwaarden, niet in een primitieve stream.
mapping
De collector mapping transformeert elementen voordat de resultaten worden doorgegeven aan een andere collector, vaak binnen een groep of partitie.
Een mapping-collector kan binnen elke groep studentennamen ophalen voordat een lijst wordt verzameld.
Meervoudige typebegrenzing
Een meervoudige typebegrenzing combineert eisen met &, zodat een typeargument aan elke typegrens moet voldoen, met een eventuele klasse voorop.
De meervoudige typebegrenzing in <T extends Number & Comparable<T>> vereist numeriek gedrag en vergelijkingsgedrag.
Methodereferentie
Een methodereferentie benoemt bestaand methode- of constructorgedrag met :: en past bij de invoer en het resultaat van een functionele interface als doeltype.
De methodereferentie Integer::parseInt levert parsegedrag aan een functie van string naar geheel getal.

N

Natuurlijke volgorde
Een natuurlijke volgorde is de volgorde die een klasse via Comparable.compareTo definieert en die wordt gebruikt wanneer geen afzonderlijke comparator is opgegeven.
De natuurlijke volgorde van strings plaatst het woord "Cherry" met een hoofdletter vóór "apple" met een kleine letter.
Neutraal element
Een neutraal element is een waarde die een andere waarde bij de bewerking van een reductie ongewijzigd laat, zoals nul voor optellen of één voor vermenigvuldigen.
Door het neutrale element 1 behoudt een productreductie haar eerste factor in plaats van het resultaat op nul te zetten.
Neveneffect
Een neveneffect is een verandering buiten de teruggegeven waarde van een berekening, zoals het wijzigen van een vastgelegde lijst of een veld van een omringend object.
Een verborgen neveneffect in een verwerkingspijplijn kan het resultaat afhankelijk maken van wijzigingen buiten de invoerwaarden.
Niet-verstoring van de bron
Niet-verstoring van de bron betekent dat gedrag in een verwerkingspijplijn de bron tijdens de verwerking ongewijzigd laat, zodat de bron geldig kan worden doorlopen.
Niet-verstoring van de bron wordt geschonden wanneer een streamactie nieuwe elementen toevoegt aan de bron-ArrayList.
Niet-wijzigbare lijst
Een niet-wijzigbare lijst wijst bewerkingen af die via de lijst haar elementen of structuur veranderen, zonder de elementobjecten onveranderlijk te maken.
De niet-wijzigbare lijst uit Stream.toList() werpt een exceptie op wanneer code een ander element probeert toe te voegen.

O

Omkering van een comparator
Een omkering van een comparator keert de volledige volgorde om die de comparator vóór de aanroep van reversed() vertegenwoordigt, inclusief bestaande aanvullende sorteercriteria.
Omkering van een comparator aan het einde van een keten op groep en score keert zowel de groeps- als de scorevolgorde om.
Onbegrensde joker
Een onbegrensde joker is ? zonder typegrens en vertegenwoordigt een onbekend argument, waarbij elementen alleen als Object kunnen worden gelezen.
Met een onbegrensde joker in List<?> kan een weergavemethode de lijstgrootte bekijken zonder het elementtype te kennen.
Ongebonden methodereferentie
Een ongebonden methodereferentie benoemt een instantiemethode via haar klasse en haalt de ontvanger uit het eerste functionele argument.
De ongebonden methodereferentie String::length roept length() aan op elke opgegeven string.
Optional
Een Optional is een container die één waarde of geen waarde vertegenwoordigt, zodat een methode een mogelijk ontbrekend resultaat kan uitdrukken.
Een Optional uit een gefilterd maximum kan leeg zijn, zodat een terugvalwaarde het ontbrekende geval afhandelt.
OptionalDouble
Een OptionalDouble vertegenwoordigt een primitieve double-waarde die kan ontbreken, zoals bij het gemiddelde van een lege numerieke stream.
Een OptionalDouble uit average() kan via orElse een gekozen terugvalwaarde van 0,0 leveren.
Overloop bij gehele getallen
Overloop bij gehele getallen treedt op wanneer een berekening met gehele getallen het weergeefbare bereik overschrijdt en de waarde omklapt, waardoor een vergelijkingsteken kan omkeren.
Door overloop bij gehele getallen is het aftrekken van grote gehele getallen met tegengesteld teken onveilig als comparatorimplementatie.

P

partitioningBy
De collector partitioningBy verdeelt elementen met een predicaat en geeft altijd beide booleaanse sleutels terug, ook als één kant leeg is.
Een partitioningBy-collector kan een lege lijst onder true teruggeven wanneer geen enkele student de drempelwaarde van het predicaat haalt.
peek
De bewerking peek geeft elementen ongewijzigd door en voert een inspectieactie uit voor doorlopen waarden; aanroepen kunnen worden overgeslagen.
Een peek die waarden weergeeft, is onbetrouwbaar voor noodzakelijk werk omdat de uitvoering de stap kan overslaan.
Predicaattoetsing
Een predicaattoetsing geeft terug of minstens één, alle of geen van de elementen van een stream aan een predicaat voldoen en kan stoppen zodra het antwoord bekend is.
Een predicaattoetsing op lege invoer geeft false voor anyMatch, maar true voor allMatch.
Predicate
Een Predicate is een standaard functionele interface voor een voorwaarde die één waarde neemt en via test een boolean teruggeeft.
Een Predicate<String> die aan removeIf wordt doorgegeven, wijst passende woorden aan die moeten worden verwijderd.
Producent: extends, consument: super
Producent: extends, consument: super is een regel om extends-jokers voor bronnen van waarden en super-jokers voor bestemmingen van waarden te kiezen.
Producent: extends, consument: super helpt bij de keuze van bron- en bestemmingsparameters voor het overzetten van lijstelementen.

R

Reductie
Een reductie combineert streamelementen met een accumulator tot één resultaat, eventueel beginnend met een neutrale beginwaarde.
Een reductie met optellen en nul berekent een totaal zonder een externe lokale teller bij te werken.
Ruw type
Een ruw type is een generieke klasse of interface die zonder typeargumenten wordt gebruikt, waardoor sommige controles van geparametriseerd gebruik worden omzeild.
Een ruw type in List xs kan een verkeerde schrijfbewerking toestaan die een List<String> zou afwijzen.

S

Samenvoegfunctie
Een samenvoegfunctie combineert twee waarden die bij dezelfde sleutel horen tot de ene waarde die een map voor die sleutel moet behouden.
De samenvoegfunctie Math::max behoudt de grootste hoeveelheid wanneer een voorraadsleutel twee keer voorkomt.
Signatuur na typewissing
Een signatuur na typewissing is een methodesignatuur waarbij parametertypen na typewissing worden beschouwd; die helpt bepalen of overladen methoden conflicteren.
De signatuur na typewissing show(Set) is gelijk voor pogingen tot overladen met Set<String> en Set<Long>.
skip
De bewerking skip laat het gevraagde aantal eerste elementen van een geordende stream weg en geeft de overige elementen door.
Na skip(2) op het bereik 1 tot en met 10 is de volgende doorgegeven waarde 3.
Sleutel-waardepaar
Een sleutel-waardepaar is een sleutel met een waarde, bekeken als één object, zodat de koppelingen van een map via een stream op sleutel of waarde kunnen worden vergeleken.
Een comparator voor sleutel-waardeparen kan de grootste voorraadhoeveelheid eerst plaatsen zonder de bronmap opnieuw te ordenen.
Sleutelextractor
Een sleutelextractor is een functie die de waarde selecteert waarmee een object wordt vergeleken, zoals de naam, groep of numerieke score.
De sleutelextractor Student::getPoints bouwt met comparingInt een numerieke studentencomparator.
Specialisatie voor primitieve typen
Een specialisatie voor primitieve typen is een variant van een functionele interface die primitieve waarden rechtstreeks accepteert of teruggeeft, zonder wrapperconversie.
De specialisatie voor primitieve typen ToIntFunction<String> levert lengtes rechtstreeks aan mapToInt.
Stabiele sortering
Een stabiele sortering behoudt de oorspronkelijke onderlinge volgorde van elementen die volgens de gebruikte ordeningsregel bij vergelijking gelijk zijn.
Een stabiele sortering houdt Ada vóór Chen wanneer beiden 85 punten hebben en Ada eerst stond.
Statische context
Een statische context hoort bij een klasse zelf en kan daarom geen typeparameter gebruiken die is verbonden aan de geparametriseerde instanties van die klasse.
In een statische context introduceert een doosfabriek een eigen <E> in plaats van de T van de klasse te gebruiken.
Stream
Een stream is een reeks die vanuit een bron door bewerkingen wordt verwerkt; een stream slaat niet bij elke stap een afzonderlijke collectie elementen op.
Een stream uit de lijst points kan de waarden tellen die aan een test voldoen en de lijst ongewijzigd laten.
Supplier
Een Supplier is een standaard functionele interface waarvan de methode get zonder argumenten een waarde van het resultaattype levert.
Een Supplier<List<String>> met ArrayList::new kan bij elke aanroep een nieuwe lijst leveren.

T

Transitiviteit
Transitiviteit vereist dat een ordening a vóór c plaatst wanneer ze a vóór b en b vóór c plaatst, zodat vergelijkingen consistent blijven.
Transitiviteit voorkomt dat een comparator een cyclus tussen drie gerangschikte objecten maakt.
Tussenbewerking
Een tussenbewerking voegt een lui uit te voeren verwerkingsstap toe en geeft een andere stream terug, zodat vóór een eindresultaat kan worden geselecteerd of getransformeerd.
De tussenbewerking filter wacht op een eindbewerking voordat elementen worden getest.
Tussenresultaat
Een tussenresultaat is een benoemde waarde tussen verwerkingsstappen, zoals een gegroepeerde map die een latere verwerkingspijplijn bevraagt.
Een tussenresultaat met gemiddelde temperaturen per stad wordt na het groeperen gefilterd om warme steden te selecteren.
Typeafleiding
Typeafleiding is het proces waarbij de compiler weggelaten type-informatie bepaalt uit argumenten en de omringende doelcontext.
Door typeafleiding levert last(words) een String op wanneer words een lijst met strings is.
Typeargument
Een typeargument is het werkelijke referentietype dat wordt opgegeven bij het gebruik van een generieke klasse, interface of methode.
Door het typeargument String geeft een getter van Box<String> tekst terug zonder expliciete typeconversie.
Typeparameter
Een typeparameter is een naam die in een generieke declaratie wordt geïntroduceerd als plaatsaanduiding voor een type dat de gebruikende code opgeeft.
De typeparameter T geeft het veld en de getter van een doos hetzelfde type dat de aanroepende code kiest.
Typewissing
Typewissing vervangt generieke typeparameters na controle door hun typegrenzen of Object, waarbij de compiler de nodige expliciete typeconversies invoegt.
Door typewissing delen objecten van een doos met strings en een doos met gehele getallen tijdens de uitvoering dezelfde klasse Box.

U

UnaryOperator
Een UnaryOperator is een functie waarvan de ene invoerwaarde en het resultaat hetzelfde type hebben, vaak gebruikt om collectie-elementen te vervangen.
Een UnaryOperator<String> kan via replaceAll van een wijzigbare lijst elk woord omzetten naar hoofdletters.

V

Vastleggen van variabelen
Vastleggen van variabelen gebeurt wanneer een lambda-expressie een lokale variabele uit de omringende code gebruikt, onder de final-beperkingen van Java.
Door het vastleggen van variabelen kan een predicaat elke invoerwaarde vergelijken met een omringende waarde limit.
Vervolgcollector
Een vervolgcollector verzamelt of vat de elementen binnen elke groep of partitie samen en bepaalt zo het waardetype van de map.
De vervolgcollector counting() verandert een map met groepslijsten in een map met tellingen van het type Long.
Verwerkingspijplijn
Een verwerkingspijplijn verbindt een bron, nul of meer tussenbewerkingen en één eindbewerking die een resultaat produceert of een actie uitvoert.
De verwerkingspijplijn van stream via filter naar count levert het aantal passende waarden als een long-waarde op.
Voortijdig stoppen zodra het resultaat vaststaat
Voortijdig stoppen zodra het resultaat vaststaat laat een bewerking eindigen zodra het resultaat bekend is, zonder noodzakelijk elk bronelement te verwerken.
Door voortijdig te stoppen zodra het resultaat vaststaat, kan anyMatch true teruggeven zodra één element aan het predicaat voldoet.

W

Waarschuwing voor niet-gecontroleerde typeveiligheid
Een waarschuwing voor niet-gecontroleerde typeveiligheid meldt een bewerking waarvan de compiler de generieke typeveiligheid niet volledig kan vaststellen, vaak door gebruik van een ruw type of een expliciete typeconversie.
Een waarschuwing voor niet-gecontroleerde typeveiligheid bij add op een ruwe lijst wijst op een mogelijke latere leesbewerking met het verkeerde type.
Wrapperklasse
Een wrapperklasse is een referentietype dat een primitieve waarde vertegenwoordigt, zodat waarden zoals gehele getallen als generieke argumenten kunnen worden gebruikt.
De wrapperklasse Integer maakt List<Integer> geldig, terwijl List<int> wordt afgewezen.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag