Zoeken…


Invoering

Lambda-expressies bieden een duidelijke en beknopte manier om een interface met één methode te implementeren met behulp van een expressie. Hiermee kunt u de hoeveelheid code verminderen die u moet maken en onderhouden. Hoewel ze vergelijkbaar zijn met anonieme klassen, hebben ze zelf geen type-informatie. Type gevolgtrekking moet gebeuren.

Methode-referenties implementeren functionele interfaces met behulp van bestaande methoden in plaats van uitdrukkingen. Ze behoren ook tot de lambda-familie.

Syntaxis

  • () -> {return expressie; } // Nul-ariteit met functielichaam om een waarde te retourneren.
  • () -> uitdrukking // steno voor bovengenoemde verklaring; er is geen puntkomma voor uitdrukkingen.
  • () -> {function-body} // Bijwerking in de lambda-expressie om bewerkingen uit te voeren.
  • parameterName -> expression // One-arity lambda-expressie. In lambda-uitdrukkingen met slechts één argument kan de haakjes worden verwijderd.
  • (Typ parameterName, Type secondParameterName, ...) -> expression // lambda die een expressie evalueert met parameters aan de linkerkant
  • (parameterName, secondParameterName, ...) -> expression // Shorthand waarmee de parametertypen voor de parameternamen worden verwijderd. Kan alleen worden gebruikt in contexten die door de compiler kunnen worden afgeleid waar de gegeven parameterlijstgrootte overeenkomt met één (en slechts één) van de verwachte grootte van de functionele interfaces.

Lambda-uitdrukkingen gebruiken om een verzameling te sorteren

Sorteerlijsten

Voorafgaand aan Java 8 was het noodzakelijk om de java.util.Comparator interface te implementeren met een anonieme (of benoemde) klasse bij het sorteren van een lijst 1 :

Java SE 1.2
List<Person> people = ...
Collections.sort(
    people,
    new Comparator<Person>() {
        public int compare(Person p1, Person p2){
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
);

Beginnend met Java 8, kan de anonieme klasse worden vervangen door een lambda-expressie. Merk op dat de types voor de parameters p1 en p2 kunnen worden weggelaten, omdat de compiler ze automatisch zal afleiden:

Collections.sort(
    people, 
    (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);

Het voorbeeld kan worden vereenvoudigd door Comparator.comparing en methodeverwijzingen te gebruiken die worden uitgedrukt met het :: (dubbele dubbele punt) symbool.

Collections.sort(
    people,
    Comparator.comparing(Person::getFirstName)
);

Met een statische import kunnen we dit bondiger uitdrukken, maar het is de vraag of dit de algehele leesbaarheid verbetert:

import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));

Vergelijkers die op deze manier zijn gebouwd, kunnen ook aan elkaar worden gekoppeld. Bijvoorbeeld, na het vergelijken van mensen door hun voornaam, als er mensen met dezelfde voornaam, de thenComparing methode met ook vergelijken op achternaam:

sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));

1 - Merk op dat Collections.sort (...) alleen werkt op collecties die subtypen van List . De Set en Collection API's impliceren geen ordening van de elementen.

Kaarten sorteren

U kunt de items van een HashMap op waarde op een vergelijkbare manier sorteren. (Merk op dat een LinkedHashMap als doel moet worden gebruikt. De toetsen in een gewone HashMap zijn niet geordend.)

Map<String, Integer> map = new HashMap();  // ... or any other Map class
// populate the map
map = map.entrySet()
    .stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue())
    .collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
                              (k, v) -> k, LinkedHashMap::new));

Inleiding tot Java-lambdas

Functionele interfaces

Lambdas kan alleen werken op een functionele interface, wat een interface is met slechts één abstracte methode. Functionele interfaces kunnen een willekeurig aantal default of static methoden hebben. (Om deze reden worden ze soms aangeduid als Single Abstract Method Interfaces, of SAM Interfaces).

interface Foo1 {
    void bar();
}

interface Foo2 {
    int bar(boolean baz);
}

interface Foo3 {
    String bar(Object baz, int mink);
}

interface Foo4 {
    default String bar() { // default so not counted
        return "baz";
    }
    void quux();
}

Bij het declareren van een functionele interface kan de annotatie @FunctionalInterface worden toegevoegd. Dit heeft geen speciaal effect, maar er wordt een compilerfout gegenereerd als deze annotatie wordt toegepast op een interface die niet functioneel is en dus een herinnering is dat de interface niet moet worden gewijzigd.

@FunctionalInterface
interface Foo5 {
    void bar();
}

@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}

@FunctionalInterface
interface Foo6 {
    void bar();
    boolean equals(Object obj); // overrides one of Object's method so not counted
}

Omgekeerd is dit geen functionele interface, omdat het meer dan één abstracte methode heeft:

interface BadFoo {
    void bar();
    void quux(); // <-- Second method prevents lambda: which one should 
                 // be considered as lambda?
}

Dit is ook geen functionele interface, omdat er geen methoden zijn:

interface BlankFoo2 { }

Let op het volgende. Stel dat je dat hebt gedaan

interface Parent { public int parentMethod(); }

en

interface Child extends Parent { public int ChildMethod(); }

Dan kan Child geen functionele interface zijn omdat het twee gespecificeerde methoden heeft.

Java 8 biedt ook een aantal generieke sjablooninterfaces in het pakket java.util.function . De ingebouwde interface Predicate<T> verpakt bijvoorbeeld een enkele methode die een waarde van type T boolean en een boolean retourneert.


Lambda-uitdrukkingen

De basisstructuur van een Lambda-expressie is:

FunctionalInterface fi = () -> System.out.println ("Hallo");

fi zal dan een enkele instantie van een klasse bevatten, vergelijkbaar met een anonieme klasse, die FunctionalInterface implementeert en waarbij de definitie van de ene methode { System.out.println("Hello"); } . Met andere woorden, het bovenstaande komt grotendeels overeen met:

FunctionalInterface fi = new FunctionalInterface() {
    @Override
    public void theOneMethod() {
        System.out.println("Hello");
    }
};

De lambda is alleen "meestal equivalent" aan de anonieme klasse omdat in een lambda de betekenis van uitdrukkingen als this , super of toString() verwijst naar de klasse waarin de toewijzing plaatsvindt, niet naar het nieuw gemaakte object.

U kunt de naam van de methode niet opgeven wanneer u een lambda gebruikt, maar dat zou u niet moeten doen, omdat een functionele interface slechts één abstracte methode moet hebben, dus Java overschrijft die methode.

In gevallen waarin het type van de lambda niet zeker is, (bijvoorbeeld overbelaste methoden), kunt u een cast aan de lambda toevoegen om de compiler te vertellen wat het type zou moeten zijn, als volgt:

Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true

Als de enkele methode van de functionele interface parameters vereist, moeten de lokale formele namen hiervan tussen de haakjes van de lambda verschijnen. Het is niet nodig om het type van de parameter aan te geven of terug te keren, omdat deze uit de interface worden gehaald (hoewel het geen fout is om de parametertypen aan te geven als je wilt). Deze twee voorbeelden zijn dus equivalent:

Foo2 longFoo = new Foo2() {
    @Override
    public int bar(boolean baz) {
        return baz ? 1 : 0;
    }
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

De haakjes rond het argument kunnen worden weggelaten als de functie slechts één argument heeft:

Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay

Impliciete retouren

Als de code in een lambda een Java- expressie is in plaats van een instructie , wordt deze behandeld als een methode die de waarde van de expressie retourneert. De volgende twee zijn dus gelijkwaardig:

IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

Toegang tot lokale variabelen (waardeafsluitingen)

Omdat lambda's syntactische steno zijn voor anonieme klassen, volgen ze dezelfde regels voor toegang tot lokale variabelen in de omsluitende scope; de variabelen moeten als final worden behandeld en niet binnen de lambda worden gewijzigd.

IntUnaryOperator makeAdder(int amount) {
    return (x) -> (x + amount); // Legal even though amount will go out of scope
                                // because amount is not modified
}

IntUnaryOperator makeAccumulator(int value) {
    return (x) -> { value += x; return value; }; // Will not compile
}

Als het nodig is om een veranderende variabele op deze manier te verpakken, moet een regulier object worden gebruikt dat een kopie van de variabele bewaart. Lees meer in Java Closures met lambda-expressies.


Lambdas accepteren

Omdat een lambda een implementatie van een interface is, hoeft er niets speciaals te worden gedaan om een methode een lambda te laten accepteren: elke functie waarvoor een functionele interface nodig is, kan ook een lambda accepteren.

public void passMeALambda(Foo1 f) {
    f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));

Het type van een lambda-expressie

Een lambda-expressie heeft op zichzelf geen specifiek type. Hoewel het waar is dat het type en het aantal parameters, samen met het type retourwaarde, wat type-informatie kan overbrengen, zal dergelijke informatie alleen beperken aan welke typen het kan worden toegewezen. De lambda ontvangt een type wanneer het op een van de volgende manieren aan een functioneel interfacetype wordt toegewezen:

  • Directe toewijzing aan een functioneel type, bijv. myPredicate = s -> s.isEmpty()
  • Doorgeven als een parameter met een functioneel type, bijv. stream.filter(s -> s.isEmpty())
  • Retourneren van een functie die een functioneel type retourneert, bijvoorbeeld return s -> s.isEmpty()
  • Casting naar een functioneel type, bijvoorbeeld (Predicate<String>) s -> s.isEmpty()

Totdat een dergelijke toewijzing aan een functioneel type is gemaakt, heeft de lambda geen bepaald type. Overweeg ter illustratie de lambda-uitdrukking o -> o.isEmpty() . Dezelfde lambda-expressie kan aan veel verschillende functionele typen worden toegewezen:

Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();

Nu ze zijn toegewezen, zijn de getoonde voorbeelden van volledig verschillende typen, hoewel de lambda-uitdrukkingen er hetzelfde uitzagen en ze niet aan elkaar kunnen worden toegewezen.

Methode Referenties

Met methodeverwijzingen kunnen vooraf gedefinieerde statische methoden of instantiemethoden die zich houden aan een compatibele functionele interface worden doorgegeven als argumenten in plaats van een anonieme lambda-expressie.

Stel dat we een model hebben:

class Person {
    private final String name;
    private final String surname;

    public Person(String name, String surname){
        this.name = name;
        this.surname = surname;
    }

    public String getName(){ return name; }
    public String getSurname(){ return surname; }
}

List<Person> people = getSomePeople();

Instantiemethode-verwijzing (naar een willekeurige instantie)

people.stream().map(Person::getName)

De equivalente lambda:

people.stream().map(person -> person.getName())

In dit voorbeeld wordt een methodeverwijzing naar de instantiemethode getName() van het type Person doorgegeven. Omdat het bekend is dat het van het verzamelingstype is, wordt de methode op de instantie (later bekend) aangeroepen.


Exemplaar methode verwijzing (naar een specifiek exemplaar)

people.forEach(System.out::println);

Aangezien System.out een instantie van PrintStream , wordt een methode-verwijzing naar deze specifieke instantie als argument doorgegeven.

De equivalente lambda:

people.forEach(person -> System.out.println(person));

Statische methode referentie

Ook voor het transformeren van streams kunnen we verwijzingen naar statische methoden toepassen:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)

Dit voorbeeld geeft een verwijzing door naar de methode static valueOf() van het type String . Daarom wordt het exemplaarobject in de verzameling als een argument doorgegeven aan valueOf() .

De equivalente lambda:

 numbers.stream().map(num -> String.valueOf(num))

Verwijzing naar een constructor

List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)

Lees Elementen van een stroom verzamelen in een verzameling om te zien hoe u elementen verzamelt voor verzameling.

De enkele String-argumentconstructor van het type Integer wordt hier gebruikt om een geheel getal te construeren met de string die als argument is opgegeven. In dit geval wordt de stream toegewezen aan gehele getallen, zolang de tekenreeks een getal vertegenwoordigt. De equivalente lambda:

strings.stream().map(s -> new Integer(s));

Spiek briefje

Formaat referentie methode Code Gelijkwaardige Lambda
Statische methode TypeName::method (args) -> TypeName.method(args)
Niet-statische methode (bijvoorbeeld * ) instance::method (args) -> instance.method(args)
Niet-statische methode (geen instantie) TypeName::method (instance, args) -> instance.method(args)
Bouwer ** TypeName::new (args) -> new TypeName(args)
Array constructor TypeName[]::new (int size) -> new TypeName[size]

* instance kan elke expressie zijn die resulteert in een verwijzing naar een instantie, bijvoorbeeld getInstance()::method , this::method

** Als TypeName een niet-statische binnenklasse is, is de constructorverwijzing alleen geldig binnen het bereik van een instantie van de buitenklasse

Implementeren van meerdere interfaces

Soms wilt u misschien een lambda-expressie die meer dan één interface implementeert. Dit is vooral handig bij markeerinterfaces (zoals java.io.Serializable ) omdat ze geen abstracte methoden toevoegen.

U wilt bijvoorbeeld een TreeSet met een aangepaste Comparator en deze vervolgens serialiseren en via het netwerk verzenden. De triviale aanpak:

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

werkt niet omdat de lambda voor de comparator geen Serializable implementeert. Je kunt dit oplossen door kruispunttypen te gebruiken en expliciet op te geven dat deze lambda serienummerbaar moet zijn:

TreeSet<Long> ts = new TreeSet<>(
    (Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

Als u vaak intersectietypes gebruikt (bijvoorbeeld als u een framework zoals Apache Spark gebruikt waarbij bijna alles serienummerbaar moet zijn), kunt u lege interfaces maken en deze in uw code gebruiken:

public interface SerializableComparator extends Comparator<Long>, Serializable {}

public class CustomTreeSet {
  public CustomTreeSet(SerializableComparator comparator) {}
}

Op deze manier weet je zeker dat de doorgegeven comparator serienummerbaar is.

Lambdas en rond uitvoeren patroon

Er zijn verschillende goede voorbeelden van het gebruik van lambdas als functionele interface in eenvoudige scenario's. Een redelijk gebruikelijk geval dat kan worden verbeterd door lambdas is wat het Execute-Around-patroon wordt genoemd. In dit patroon hebt u een set standaardconfiguratie- / verwijderingscode die nodig is voor meerdere scenario's rond gebruikscode-specifieke code. Een paar veelvoorkomend voorbeeld hiervan zijn file io, database io, try / catch-blokken.

interface DataProcessor {
    void process( Connection connection ) throws SQLException;;
}

public void doProcessing( DataProcessor processor ) throws SQLException{
    try (Connection connection = DBUtil.getDatabaseConnection();) {
        processor.process(connection);
        connection.commit();
    } 
}

Om deze methode vervolgens met een lambda aan te roepen, kan het er als volgt uitzien:

public static void updateMyDAO(MyVO vo) throws DatabaseException {
    doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}

Dit is niet beperkt tot I / O-bewerkingen. Het kan van toepassing zijn op elk scenario waarin vergelijkbare setup- / afbouwtaken van toepassing zijn met kleine variaties. Het belangrijkste voordeel van dit patroon is het hergebruik van codes en het afdwingen van DRY (niet herhalen).

Lambda-expressie gebruiken met uw eigen functionele interface

Lambdas zijn bedoeld om inline implementatiecode te bieden voor interfaces met één methode en de mogelijkheid om ze door te geven zoals we dat met normale variabelen hebben gedaan. We noemen ze functionele interface.

Het schrijven van een Runnable in anonieme klas en het starten van een discussie ziet er bijvoorbeeld als volgt uit:

//Old way
new Thread(
        new Runnable(){
            public void run(){
                System.out.println("run logic...");
            }
        }
).start();

//lambdas, from Java 8
new Thread(
        ()-> System.out.println("run logic...")
).start();

Laten we nu, in lijn met hierboven, zeggen dat u een aangepaste interface hebt:

interface TwoArgInterface {
    int operate(int a, int b);
}

Hoe gebruik je lambda om implementatie van deze interface in je code te geven? Hetzelfde als het hierboven weergegeven voorbeeld. Zie het onderstaande stuurprogramma:

public class CustomLambda {
    public static void main(String[] args) {

        TwoArgInterface plusOperation = (a, b) -> a + b;
        TwoArgInterface divideOperation = (a,b)->{
            if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
            return a/b;
        };

        System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
        System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));

    }
}

`return` keert alleen terug van de lambda, niet de uiterlijke methode

De return keert alleen terug van de lambda, niet de externe methode.

Let op dat dit anders is dan Scala en Kotlin!

void threeTimes(IntConsumer r) {
  for (int i = 0; i < 3; i++) {
    r.accept(i);
  }
}

void demo() {
  threeTimes(i -> {
    System.out.println(i);
    return; // Return from lambda to threeTimes only!
  });
}

Dit kan leiden tot onverwacht gedrag bij het proberen om eigen taalconstructies te schrijven, omdat in ingebouwde constructen zoals for loops return zich anders gedraagt:

void demo2() {
  for (int i = 0; i < 3; i++) {
    System.out.println(i);
    return; // Return from 'demo2' entirely
  }
}

In Scala en Kotlin zouden demo en demo2 beide alleen 0 afdrukken. Maar dit is niet consistenter. De Java-aanpak is consistent met refactoring en het gebruik van klassen - het return in de code bovenaan en de onderstaande code gedraagt zich hetzelfde:

void demo3() {
  threeTimes(new MyIntConsumer());
}

class MyIntConsumer implements IntConsumer {
  public void accept(int i) {
    System.out.println(i);
    return;
  }
}

Daarom is de Java- return meer consistent met klassemethoden en refactoring, maar minder met de for en while builds, deze blijven speciaal.

Hierdoor zijn de volgende twee equivalent in Java:

IntStream.range(1, 4)
    .map(x -> x * x)
    .forEach(System.out::println);
IntStream.range(1, 4)
    .map(x -> { return x * x; })
    .forEach(System.out::println);

Bovendien is het gebruik van try-with-resources veilig in Java:

class Resource implements AutoCloseable {
  public void close() { System.out.println("close()"); }
}

void executeAround(Consumer<Resource> f) {
  try (Resource r = new Resource()) {
    System.out.print("before ");
    f.accept(r);
    System.out.print("after ");
  }
}

void demo4() {
  executeAround(r -> {
    System.out.print("accept() ");
    return; // Does not return from demo4, but frees the resource.
  });
}

wordt afgedrukt before accept() after close() . In de semantiek Scala en Kotlin zouden de try-with-resources niet worden gesloten, maar zou alleen worden afgedrukt before accept() .

Java-sluitingen met lambda-uitdrukkingen.

Een lambdasluiting wordt gemaakt wanneer een lambda-expressie verwijst naar de variabelen van een omsluitend bereik (globaal of lokaal). De regels hiervoor zijn dezelfde als die voor inline-methoden en anonieme klassen.

Lokale variabelen uit een omsluitende scope die binnen een lambda worden gebruikt, moeten final . Met Java 8 (de vroegste versie die lambdas ondersteunt) hoeven ze niet in de externe context final te worden verklaard , maar moeten op die manier worden behandeld. Bijvoorbeeld:

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

Dit is legaal zolang de waarde van de variabele n niet wordt gewijzigd. Als u probeert de variabele binnen of buiten de lambda te wijzigen, krijgt u de volgende compilatiefout:

"lokale variabelen waarnaar wordt verwezen vanuit een lambda-expressie moeten definitief of effectief definitief zijn ".

Bijvoorbeeld:

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

Als het nodig is om een veranderende variabele binnen een lambda te gebruiken, is de normale aanpak om een final kopie van de variabele te declareren en de kopie te gebruiken. Bijvoorbeeld

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

Natuurlijk ziet het lichaam van de lambda de wijzigingen in de oorspronkelijke variabele niet.

Merk op dat Java geen echte sluitingen ondersteunt. Een Java-lambda kan niet worden gemaakt op een manier die het mogelijk maakt veranderingen in de omgeving te zien waarin deze is geïnstantieerd. Als u een afsluiting wilt implementeren die de omgeving waarneemt of wijzigt, moet u deze simuleren met een gewone klasse. Bijvoorbeeld:

// Does not compile ...
public IntUnaryOperator createAccumulator() {
    int value = 0;
    IntUnaryOperator accumulate = (x) -> { value += x; return value; };
    return accumulate;
}

Het bovenstaande voorbeeld zal niet compileren om eerder besproken redenen. We kunnen de compilatiefout als volgt omzeilen:

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
    private int value = 0;

    public IntUnaryOperator createAccumulator() {
        IntUnaryOperator accumulate = (x) -> { value += x; return value; };
        return accumulate;
    }
}

Het probleem is dat hierdoor het ontwerpcontract voor de IntUnaryOperator interface wordt IntUnaryOperator , waarin staat dat instanties functioneel en stateloos moeten zijn. Als een dergelijke sluiting wordt doorgegeven aan ingebouwde functies die functionele objecten accepteren, kan dit crashes of onjuist gedrag veroorzaken. Sluitingen die de veranderlijke status inkapselen, moeten als reguliere klassen worden geïmplementeerd. Bijvoorbeeld.

// Correct ...
public class Accumulator {
   private int value = 0;

   public int accumulate(int x) {
      value += x;
      return value;
   }
}

Lambda - luisteraarvoorbeeld

Anonieme luisteraar

Vóór Java 8 is het heel gebruikelijk dat een anonieme klasse wordt gebruikt om de klikgebeurtenis van een JButton af te handelen, zoals weergegeven in de volgende code. Dit voorbeeld laat zien hoe een anonieme luisteraar kan worden geïmplementeerd binnen het bereik van btn.addActionListener .

JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button was pressed");
    }
});

Lambda-luisteraar

Omdat de ActionListener interface slechts één methode actionPerformed() definieert, is het een functionele interface, wat betekent dat er een plaats is om Lambda-expressies te gebruiken om de boilerplate-code te vervangen. Het bovenstaande voorbeeld kan als volgt worden herschreven met Lambda-expressies:

JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
    System.out.println("Button was pressed");
});

Traditionele stijl tot Lambda-stijl

Traditionele manier

interface MathOperation{
    boolean unaryOperation(int num);
}

public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = new MathOperation() {
            @Override
            public boolean unaryOperation(int num) {
                return num%2 == 0;
            }
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}

Lambda-stijl

  1. Verwijder de klassenaam en de functionele interface-inhoud.
public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = (int num) -> {
            return num%2 == 0;
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}
  1. Optionele typeaangifte
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. Optionele haakjes rond parameter, als het een enkele parameter is
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. Optionele gekrulde accolades, als er maar één regel in het functielichaam is
  2. Optioneel retourwoord, als er maar één regel in de hoofdtekst is
MathOperation isEven = num -> num%2 == 0;

Lambdas en geheugengebruik

Omdat Java-lambdas sluitingen zijn, kunnen ze de waarden van variabelen in de omringende lexicale scope "vastleggen". Hoewel niet alle lambdas iets vastleggen - eenvoudige lambdas zoals s -> s.length() niets vastleggen en stateloos worden genoemd - vereisen het vastleggen van lambdas een tijdelijk object om de vastgelegde variabelen te bevatten. In dit codefragment is de lambda () -> j een vangende lambda en kan een object worden toegewezen wanneer het wordt geëvalueerd:

public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000000000; i++) {
        int j = i;
        doSomethingWithLambda(() -> j);
    }
}

Hoewel het misschien niet meteen duidelijk is, omdat het new trefwoord nergens in het fragment voorkomt, kan deze code 1.000.000.000 afzonderlijke objecten maken om de instanties van de uitdrukking () -> j lambda weer te geven. Er moet echter ook worden opgemerkt dat toekomstige versies van Java 1 dit kunnen optimaliseren zodat tijdens runtime de lambda-exemplaren werden hergebruikt of op een andere manier werden weergegeven.


1 - Java 9 introduceert bijvoorbeeld een optionele "koppelingsfase" in de Java-build-reeks die de mogelijkheid biedt om dergelijke globale optimalisaties uit te voeren.

Lambda-uitdrukkingen en predikaten gebruiken om een bepaalde waarde (n) uit een lijst te krijgen

Vanaf Java 8 kunt u lambda-uitdrukkingen en predicaten gebruiken.

Voorbeeld: gebruik een lambda-expressie en een predikaat om een bepaalde waarde uit een lijst te krijgen. In dit voorbeeld wordt elke persoon afgedrukt met het feit of ze 18 jaar of ouder zijn of niet.

Persoonsklasse:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() { return age; }
    public String getName() { return name; }
}

De ingebouwde interface Predicate van de java.util.function.Predicate-pakketten is een functionele interface met een boolean test(T t) -methode.

Voorbeeld gebruik:

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

public class LambdaExample {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jeroen", 20));
        personList.add(new Person("Jack", 5));
        personList.add(new Person("Lisa", 19));

        print(personList, p -> p.getAge() >= 18);
    }

    private static void print(List<Person> personList, Predicate<Person> checker) {
        for (Person person : personList) {
            if (checker.test(person)) {
                System.out.print(person + " matches your expression.");
            } else {
                System.out.println(person  + " doesn't match your expression.");
            }
        }
    }
}

De print(personList, p -> p.getAge() >= 18); methode neemt een lambda-expressie (omdat het predicaat een parameter wordt gebruikt) waar u de benodigde expressie kunt definiëren. De testmethode van de checker controleert of deze expressie correct is of niet: checker.test(person) .

U kunt dit eenvoudig in iets anders veranderen, bijvoorbeeld om print(personList, p -> p.getName().startsWith("J")); te print(personList, p -> p.getName().startsWith("J")); . Dit zal controleren of de naam van de persoon begint met een "J".



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow