CodeGym /Java blogg /Slumpmässig /En förklaring av lambda-uttryck i Java. Med exempel och u...
John Squirrels
Nivå
San Francisco

En förklaring av lambda-uttryck i Java. Med exempel och uppgifter. Del 2

Publicerad i gruppen
Vem är den här artikeln till för?
  • Det är för personer som läser den första delen av denna artikel;
  • Det är för personer som tror att de redan känner till Java Core väl, men som inte har någon aning om lambda-uttryck i Java. Eller så har de kanske hört något om lambda-uttryck, men detaljerna saknas.
  • Det är för personer som har en viss förståelse för lambda-uttryck, men som fortfarande är skrämda av dem och ovana vid att använda dem.
Om du inte passar in i någon av dessa kategorier kan du tycka att den här artikeln är tråkig, felaktig eller i allmänhet inte din kopp te. I det här fallet, gå gärna vidare till andra saker eller, om du är väl insatt i ämnet, vänligen ge förslag i kommentarerna om hur jag kan förbättra eller komplettera artikeln. En förklaring av lambda-uttryck i Java.  Med exempel och uppgifter.  Del 2 - 1Materialet gör inte anspråk på att ha något akademiskt värde, än mindre nyhet. Snarare tvärtom: Jag ska försöka beskriva saker som är komplexa (för vissa personer) så enkelt som möjligt. En begäran om att förklara Stream API inspirerade mig att skriva detta. Jag funderade på det och bestämde mig för att några av mina streamexempel skulle vara obegripliga utan förståelse för lambda-uttryck. Så vi börjar med lambda-uttryck.

Tillgång till externa variabler

Kompilerar den här koden med en anonym klass?

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

    @Override 
    public void run() { 
        counter++;
    }
};
Nej. counter Variabeln måste vara final. Eller om inte final, så kan den åtminstone inte ändra sitt värde. Samma princip gäller i lambda-uttryck. De kan komma åt alla variabler som de kan "se" från den plats de deklareras. Men en lambda får inte ändra dem (tilldela dem ett nytt värde). Det finns dock ett sätt att kringgå denna begränsning i anonyma klasser. Skapa helt enkelt en referensvariabel och ändra objektets interna tillstånd. Genom att göra det ändras inte variabeln i sig (pekar på samma objekt) och kan säkert markeras som final.

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

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Här counterär vår variabel en referens till ett AtomicIntegerobjekt. Och incrementAndGet()metoden används för att ändra tillståndet för detta objekt. Värdet på variabeln i sig ändras inte medan programmet körs. Den pekar alltid på samma objekt, vilket låter oss deklarera variabeln med det sista nyckelordet. Här är samma exempel, men med lambda-uttryck:

int counter = 0;
Runnable r = () -> counter++;
Detta kommer inte att kompileras av samma anledning som versionen med en anonym klass:  counterfår inte ändras medan programmet körs. Men allt är bra om vi gör så här:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Detta gäller även anropsmetoder. Inom lambda-uttryck kan du inte bara komma åt alla "synliga" variabler, utan även anropa alla tillgängliga 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!");
    }
}
Även om staticMethod()den är privat är den tillgänglig inuti main()metoden, så den kan också anropas inifrån en lambda som skapats i mainmetoden.

När exekveras ett lambdauttryck?

Du kanske tycker att följande fråga är för enkel, men du bör ställa den på samma sätt: när kommer koden inuti lambda-uttrycket att exekveras? När det skapas? Eller när det heter (vilket ännu inte är känt)? Detta är ganska lätt att kontrollera.

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ärmutgång:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Du kan se att lambda-uttrycket kördes i slutet, efter att tråden skapats och först när programmets exekvering når metoden run(). Absolut inte när det deklareras. Genom att deklarera ett lambda-uttryck har vi bara skapat ett Runnableobjekt och beskrivit hur dess run()metod beter sig. Själva metoden exekveras långt senare.

Metodreferenser?

Metodreferenser är inte direkt relaterade till lambdas, men jag tycker att det är vettigt att säga några ord om dem i den här artikeln. Anta att vi har ett lambda-uttryck som inte gör något speciellt, utan bara kallar en metod.

x -> System.out.println(x)
Den tar emot några xoch bara samtal System.out.println(), passerar in x. I det här fallet kan vi ersätta det med en hänvisning till den önskade metoden. Så här:

System.out::println
Det stämmer — inga parenteser i slutet! Här är ett mer komplett exempel:

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

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

strings.forEach(x -> System.out.println(x));
På sista raden använder vi forEach()metoden, som tar ett objekt som implementerar gränssnittet Consumer. Återigen, detta är ett funktionellt gränssnitt som bara har en void accept(T t)metod. Följaktligen skriver vi ett lambda-uttryck som har en parameter (eftersom det skrivs i själva gränssnittet anger vi inte parametertypen, vi anger bara att vi kommer att kalla den ) x. I kroppen av lambda-uttrycket skriver vi koden som kommer att exekveras när metoden accept()anropas. Här visar vi helt enkelt vad som hamnat i xvariabeln. Samma forEach()metod itererar genom alla element i samlingen och kallar accept()metoden för implementeringen avConsumergränssnitt (vår lambda), passerar in varje föremål i samlingen. Som sagt kan vi ersätta ett sådant lambda-uttryck (ett som helt enkelt klassar en annan metod) med en referens till den önskade metoden. Då kommer vår kod att se ut så här:

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

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

strings.forEach(System.out::println);
Huvudsaken är att parametrarna för println()och accept()metoderna matchar. Eftersom println()metoden kan acceptera vad som helst (den är överbelastad för alla primitiva typer och alla objekt), istället för lambda-uttryck, kan vi helt enkelt skicka en referens till metoden println()till forEach(). Sedan forEach()tar varje element i samlingen och skickar det direkt till println()metoden. För alla som stöter på detta för första gången, observera att vi inte ringer ( System.out.println()med prickar mellan ord och med parentes i slutet). Istället skickar vi en hänvisning till denna metod. Om vi ​​skriver detta

strings.forEach(System.out.println());
vi kommer att ha ett kompileringsfel. Innan anropet till forEach()ser Java att det System.out.println()anropas, så det förstår att returvärdet är voidoch kommer att försöka skicka voidtill forEach(), som istället förväntar sig ett Consumerobjekt.

Syntax för metodreferenser

Det är ganska enkelt:
  1. Vi skickar en referens till en statisk metod som denna: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 skickar en referens till en icke-statisk metod med ett befintligt objekt, så här: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 skickar en referens till en icke-statisk metod med klassen som implementerar den enligt följande: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 skickar en referens till en konstruktör så här:ClassName::new

    Metodreferenser är mycket praktiska när du redan har en metod som skulle fungera perfekt som en återuppringning. I det här fallet, istället för att skriva ett lambdauttryck som innehåller metodens kod, eller skriva ett lambdauttryck som helt enkelt anropar metoden, skickar vi helt enkelt en referens till den. Och det är allt.

En intressant skillnad mellan anonyma klasser och lambda-uttryck

I en anonym klass thispekar nyckelordet på ett objekt i den anonyma klassen. Men om vi använder detta i en lambda får vi tillgång till objektet för den innehållande klassen. Den där vi faktiskt skrev lambdauttrycket. Detta beror på att lambda-uttryck är kompilerade till en privat metod för den klass de är skrivna i. Jag skulle inte rekommendera att använda denna "funktion", eftersom den har en bieffekt och som strider mot principerna för funktionell programmering. Som sagt, detta tillvägagångssätt är helt förenligt med OOP. ;)

Var fick jag min information och vad mer bör du läsa?

Och såklart hittade jag massor av saker på Google :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION