CodeGym /Java blog /Tilfældig /En forklaring af lambda-udtryk i Java. Med eksempler og o...
John Squirrels
Niveau
San Francisco

En forklaring af lambda-udtryk i Java. Med eksempler og opgaver. Del 2

Udgivet i gruppen
Hvem er denne artikel til?
  • Det er for folk, der læser den første del af denne artikel;
  • Det er for folk, der tror, ​​de allerede kender Java Core godt, men som ikke har nogen anelse om lambda-udtryk i Java. Eller måske har de hørt noget om lambdaudtryk, men detaljerne mangler.
  • Det er for folk, der har en vis forståelse af lambda-udtryk, men som stadig er forskrækket af dem og uvant til at bruge dem.
Hvis du ikke passer ind i en af ​​disse kategorier, kan du finde denne artikel kedelig, mangelfuld eller generelt ikke din kop te. I dette tilfælde er du velkommen til at gå videre til andre ting, eller, hvis du er velbevandret i emnet, bedes du komme med forslag i kommentarerne til, hvordan jeg kan forbedre eller supplere artiklen. En forklaring af lambda-udtryk i Java.  Med eksempler og opgaver.  Del 2 - 1Materialet hævder ikke at have nogen akademisk værdi, endsige nyhed. Tværtimod: Jeg vil forsøge at beskrive ting, der er komplekse (for nogle mennesker) så enkelt som muligt. En anmodning om at forklare Stream API inspirerede mig til at skrive dette. Jeg tænkte over det og besluttede, at nogle af mine stream-eksempler ville være uforståelige uden en forståelse af lambda-udtryk. Så vi starter med lambda-udtryk.

Adgang til eksterne variabler

Kompilerer denne kode med en anonym klasse?

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

    @Override 
    public void run() { 
        counter++;
    }
};
Nej. counter Variablen skal være final. Eller hvis ikke final, så kan den i det mindste ikke ændre sin værdi. Det samme princip gælder i lambda-udtryk. De kan få adgang til alle de variable, som de kan "se" fra det sted, de er deklareret. Men en lambda må ikke ændre dem (tildel dem en ny værdi). Der er dog en måde at omgå denne begrænsning i anonyme klasser. Du skal blot oprette en referencevariabel og ændre objektets interne tilstand. Derved ændrer variablen sig ikke (peger på det samme objekt) og kan sikkert markeres som final.

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

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Her er vores countervariabel en reference til et AtomicIntegerobjekt. Og incrementAndGet()metoden bruges til at ændre tilstanden af ​​dette objekt. Værdien af ​​selve variablen ændres ikke, mens programmet kører. Det peger altid på det samme objekt, som lader os erklære variablen med det endelige nøgleord. Her er de samme eksempler, men med lambda-udtryk:

int counter = 0;
Runnable r = () -> counter++;
Dette vil ikke kompilere af samme grund som versionen med en anonym klasse:  countermå ikke ændres, mens programmet kører. Men alt er fint, hvis vi gør det sådan her:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Dette gælder også for opkaldsmetoder. Indenfor lambda-udtryk kan du ikke kun få adgang til alle "synlige" variabler, men også kalde alle tilgængelige 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!");
    }
}
Selvom staticMethod()den er privat, er den tilgængelig inde i main()metoden, så den kan også kaldes inde fra en lambda oprettet i metoden main.

Hvornår udføres et lambda-udtryk?

Du finder måske følgende spørgsmål for simpelt, men du bør stille det samme: hvornår vil koden inde i lambda-udtrykket blive eksekveret? Hvornår er det oprettet? Eller hvornår det hedder (som endnu ikke vides)? Dette er ret nemt at kontrollere.

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(); 
Skærmudgang:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Du kan se, at lambda-udtrykket blev eksekveret til allersidst, efter tråden blev oprettet og først når programmets eksekvering når frem til metoden run(). I hvert fald ikke når det er deklareret. Ved at erklære et lambda-udtryk har vi kun oprettet et Runnableobjekt og beskrevet, hvordan dets run()metode opfører sig. Selve metoden udføres meget senere.

Metode referencer?

Metodereferencer er ikke direkte relateret til lambdaer, men jeg synes, det giver mening at sige et par ord om dem i denne artikel. Antag, at vi har et lambda-udtryk, der ikke gør noget særligt, men blot kalder en metode.

x -> System.out.println(x)
Den modtager nogle xog bare opkald System.out.println(), passerer ind x. I dette tilfælde kan vi erstatte det med en henvisning til den ønskede metode. Sådan her:

System.out::println
Det er rigtigt - ingen parentes til sidst! Her er et mere komplet eksempel:

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

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

strings.forEach(x -> System.out.println(x));
I sidste linje bruger vi forEach()metoden, som tager et objekt, der implementerer Consumergrænsefladen. Igen er dette en funktionel grænseflade, der kun har én void accept(T t)metode. Derfor skriver vi et lambda-udtryk, der har én parameter (fordi det er indtastet i selve grænsefladen, angiver vi ikke parametertypen; vi angiver kun, at vi vil kalde det x). I teksten til lambda-udtrykket skriver vi den kode, der vil blive udført, når metoden accept()kaldes. Her viser vi blot, hvad der endte i xvariablen. Den samme forEach()metode gentager alle elementerne i samlingen og kalder metoden accept()for implementeringen afConsumergrænseflade (vores lambda), der passerer hvert element i samlingen. Som sagt kan vi erstatte sådan et lambda-udtryk (et der blot klassificerer en anden metode) med en reference til den ønskede metode. Så vil vores kode se sådan ud:

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

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

strings.forEach(System.out::println);
Det vigtigste er, at parametrene for println()og accept()metoderne stemmer overens. Fordi println()metoden kan acceptere hvad som helst (den er overbelastet for alle primitive typer og alle objekter), i stedet for lambda-udtryk, kan vi blot sende en reference til metoden println()til forEach(). Derefter forEach()tager hvert element i samlingen og videregiver det direkte til metoden println(). For alle, der støder på dette for første gang, bemærk venligst, at vi ikke ringer System.out.println()(med prikker mellem ordene og med parentes til sidst). I stedet sender vi en henvisning til denne metode. Hvis vi skriver dette

strings.forEach(System.out.println());
vi vil have en kompileringsfejl. Inden kaldet til forEach(), ser Java, at det System.out.println()bliver kaldt, så det forstår, at returværdien er voidog vil forsøge at gå videre voidtil forEach(), som i stedet forventer et Consumerobjekt.

Syntaks for metodereferencer

Det er ret simpelt:
  1. Vi videregiver en henvisning til en statisk metode som denne: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 reference til en ikke-statisk metode ved hjælp af 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 reference til en ikke-statisk metode ved hjælp af klassen, der 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 henvisning til en konstruktør som denne:ClassName::new

    Metodereferencer er meget praktiske, når du allerede har en metode, der ville fungere perfekt som et tilbagekald. I dette tilfælde, i stedet for at skrive et lambda-udtryk, der indeholder metodens kode, eller at skrive et lambda-udtryk, der blot kalder metoden, sender vi blot en reference til den. Og det er det.

En interessant skelnen mellem anonyme klasser og lambda-udtryk

I en anonym klasse thispeger nøgleordet på et objekt fra den anonyme klasse. Men hvis vi bruger dette inde i en lambda, får vi adgang til objektet for den indeholdende klasse. Den, hvor vi faktisk skrev lambda-udtrykket. Dette sker, fordi lambda-udtryk er kompileret til en privat metode af den klasse, de er skrevet i. Jeg vil ikke anbefale at bruge denne "funktion", da den har en bivirkning, og det er i modstrid med principperne for funktionel programmering. Når det er sagt, er denne tilgang helt i overensstemmelse med OOP. ;)

Hvor har jeg fået mine oplysninger, og hvad skal du ellers læse?

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