CodeGym /Java-blogg /Tilfeldig /En forklaring av lambda-uttrykk i Java. Med eksempler og ...
John Squirrels
Nivå
San Francisco

En forklaring av lambda-uttrykk i Java. Med eksempler og oppgaver. Del 2

Publisert i gruppen
Hvem er denne artikkelen for?
  • Det er for folk som leser den første delen av denne artikkelen;
  • Det er for folk som tror de allerede kjenner Java Core godt, men som ikke har peiling på lambda-uttrykk i Java. Eller kanskje de har hørt noe om lambda-uttrykk, men detaljene mangler.
  • Det er for folk som har en viss forståelse av lambda-uttrykk, men som fortsatt er skremt av dem og uvant med å bruke dem.
Hvis du ikke passer inn i en av disse kategoriene, kan det hende du synes denne artikkelen er kjedelig, defekt eller generelt sett ikke er din kopp te. I dette tilfellet kan du gjerne gå videre til andre ting eller, hvis du er godt kjent med emnet, vennligst kom med forslag i kommentarene om hvordan jeg kan forbedre eller supplere artikkelen. En forklaring av lambda-uttrykk i Java.  Med eksempler og oppgaver.  Del 2 - 1Materialet hevder ikke å ha noen akademisk verdi, enn si nyhet. Tvert imot: Jeg vil prøve å beskrive ting som er komplekse (for noen mennesker) så enkelt som mulig. En forespørsel om å forklare Stream API inspirerte meg til å skrive dette. Jeg tenkte på det og bestemte meg for at noen av strømeksemplene mine ville være uforståelige uten forståelse av lambda-uttrykk. Så vi starter med lambda-uttrykk.

Tilgang til eksterne variabler

Kompilerer denne koden med en anonym klasse?

int counter = 0;
Runnable r = new Runnable() { 

    @Override 
    public void run() { 
        counter++;
    }
};
Nei. counter Variabelen må være final. Eller hvis ikke final, så kan den i det minste ikke endre verdien. Det samme prinsippet gjelder i lambda-uttrykk. De kan få tilgang til alle variablene de kan "se" fra stedet de er deklarert. Men en lambda må ikke endre dem (gi dem en ny verdi). Det er imidlertid en måte å omgå denne begrensningen i anonyme klasser. Bare lag en referansevariabel og endre objektets interne tilstand. Ved å gjøre det endres ikke selve variabelen (peker på samme objekt) og kan trygt merkes som final.

final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() { 

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Her er vår countervariabel en referanse til et AtomicIntegerobjekt. Og incrementAndGet()metoden brukes til å endre tilstanden til dette objektet. Verdien av selve variabelen endres ikke mens programmet kjører. Det peker alltid til det samme objektet, som lar oss deklarere variabelen med det endelige nøkkelordet. Her er de samme eksemplene, men med lambda-uttrykk:

int counter = 0;
Runnable r = () -> counter++;
Dette vil ikke kompilere av samme grunn som versjonen med en anonym klasse:  countermå ikke endres mens programmet kjører. Men alt er bra hvis vi gjør det slik:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Dette gjelder også for ringemetoder. Innenfor lambda-uttrykk kan du ikke bare få tilgang til alle "synlige" variabler, men også kalle alle tilgjengelige metoder.

public class Main { 

    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    } 

    private static void staticMethod() { 

        System.out.println("I'm staticMethod(), and someone just called me!");
    }
}
Selv om staticMethod()den er privat, er den tilgjengelig inne i main()metoden, så den kan også kalles fra innsiden av en lambda opprettet i metoden main.

Når utføres et lambda-uttrykk?

Du finner kanskje følgende spørsmål for enkelt, men du bør stille det samme: når vil koden i lambda-uttrykket bli utført? Når den er opprettet? Eller når det kalles (som ennå ikke er kjent)? Dette er ganske enkelt å sjekke.

System.out.println("Program start"); 

// All sorts of code here
// ...

System.out.println("Before lambda declaration");

Runnable runnable = () -> System.out.println("I'm a lambda!");

System.out.println("After lambda declaration"); 

// All sorts of other code here
// ...

System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start(); 
Skjermutgang:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Du kan se at lambda-uttrykket ble utført helt på slutten, etter at tråden ble opprettet og først når programmets utførelse når metoden run(). Absolutt ikke når det er deklarert. Ved å erklære et lambda-uttrykk har vi bare laget et Runnableobjekt og beskrevet hvordan run()metoden oppfører seg. Selve metoden utføres mye senere.

Metodereferanser?

Metodereferanser er ikke direkte relatert til lambdaer, men jeg tror det er fornuftig å si noen ord om dem i denne artikkelen. Anta at vi har et lambda-uttrykk som ikke gjør noe spesielt, men bare kaller en metode.

x -> System.out.println(x)
Den mottar noen xog bare anrop System.out.println()og går inn x. I dette tilfellet kan vi erstatte den med en referanse til ønsket metode. Som dette:

System.out::println
Det stemmer - ingen parentes på slutten! Her er et mer fullstendig eksempel:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo"); 

strings.forEach(x -> System.out.println(x));
På den siste linjen bruker vi forEach()metoden, som tar et objekt som implementerer Consumergrensesnittet. Igjen, dette er et funksjonelt grensesnitt som har bare én void accept(T t)metode. Følgelig skriver vi et lambda-uttrykk som har én parameter (fordi det er skrevet inn i selve grensesnittet, spesifiserer vi ikke parametertypen; vi indikerer bare at vi vil kalle den ) x. I kroppen til lambda-uttrykket skriver vi koden som skal kjøres når metoden accept()kalles. Her viser vi ganske enkelt hva som havnet i xvariabelen. Den samme forEach()metoden itererer gjennom alle elementene i samlingen og kaller accept()metoden for implementering avConsumergrensesnitt (vår lambda), passerer inn hvert element i samlingen. Som sagt kan vi erstatte et slikt lambda-uttrykk (et som ganske enkelt klassifiserer en annen metode) med en referanse til ønsket metode. Da vil koden vår se slik ut:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo");

strings.forEach(System.out::println);
Det viktigste er at parametrene til println()og accept()metodene stemmer overens. Fordi println()metoden kan akseptere hva som helst (den er overbelastet for alle primitive typer og alle objekter), i stedet for lambda-uttrykk, kan vi ganske enkelt sende en referanse til metoden println()til forEach(). Deretter forEach()tar hvert element i samlingen og sender det direkte til metoden println(). For alle som møter dette for første gang, vær oppmerksom på at vi ikke ringer System.out.println()(med prikker mellom ordene og med parentes på slutten). I stedet sender vi en referanse til denne metoden. Hvis vi skriver dette

strings.forEach(System.out.println());
vi vil ha en kompileringsfeil. Før kallet til forEach(), ser Java at det System.out.println()blir kalt, så det forstår at returverdien er voidog vil prøve å sende voidtil forEach(), som i stedet forventer et Consumerobjekt.

Syntaks for metodereferanser

Det er ganske enkelt:
  1. Vi sender en referanse til en statisk metode som dette:ClassName::staticMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>(); 
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            strings.forEach(Main::staticMethod); 
        } 
    
        private static void staticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  2. Vi sender en referanse til en ikke-statisk metode ved å bruke et eksisterende objekt, som dette:objectName::instanceMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>();
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            Main instance = new Main(); 
            strings.forEach(instance::nonStaticMethod); 
        } 
    
        private void nonStaticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  3. Vi sender en referanse til en ikke-statisk metode ved å bruke klassen som implementerer den som følger:ClassName::methodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<User> users = new LinkedList<>(); 
            users.add (new User("John")); 
            users.add(new User("Paul")); 
            users.add(new User("George")); 
    
            users.forEach(User::print); 
        } 
    
        private static class User { 
            private String name; 
    
            private User(String name) { 
                this.name = name; 
            } 
    
            private void print() { 
                System.out.println(name); 
            } 
        } 
    }
    
  4. Vi sender en referanse til en konstruktør som dette:ClassName::new

    Metodereferanser er veldig praktisk når du allerede har en metode som fungerer perfekt som tilbakeringing. I dette tilfellet, i stedet for å skrive et lambda-uttrykk som inneholder metodens kode, eller skrive et lambda-uttrykk som ganske enkelt kaller metoden, sender vi bare en referanse til den. Og det er det.

Et interessant skille mellom anonyme klasser og lambda-uttrykk

I en anonym klasse thispeker nøkkelordet til et objekt fra den anonyme klassen. Men hvis vi bruker dette inne i en lambda, får vi tilgang til objektet til den inneholdende klassen. Den der vi faktisk skrev lambda-uttrykket. Dette skjer fordi lambda-uttrykk er kompilert til en privat metode av klassen de er skrevet i. Jeg vil ikke anbefale å bruke denne "funksjonen", siden den har en bieffekt og som motsier prinsippene for funksjonell programmering. Når det er sagt, er denne tilnærmingen helt i samsvar med OOP. ;)

Hvor fikk jeg informasjonen min og hva annet bør du lese?

Og selvfølgelig fant jeg massevis av ting på Google :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION