CodeGym /Java Blog /Random /Isang paliwanag ng mga expression ng lambda sa Java. Sa m...
John Squirrels
Antas
San Francisco

Isang paliwanag ng mga expression ng lambda sa Java. Sa mga halimbawa at gawain. Bahagi 2

Nai-publish sa grupo
Para kanino ang artikulong ito?
  • Ito ay para sa mga taong nagbabasa ng unang bahagi ng artikulong ito;
  • Ito ay para sa mga taong nag-iisip na alam na nila ang Java Core, ngunit walang ideya tungkol sa mga expression ng lambda sa Java. O baka may narinig na sila tungkol sa mga expression ng lambda, ngunit kulang ang mga detalye.
  • Ito ay para sa mga taong may tiyak na pag-unawa sa mga expression ng lambda, ngunit natatakot pa rin sa mga ito at hindi sanay na gamitin ang mga ito.
Kung hindi ka magkasya sa isa sa mga kategoryang ito, maaari mong makita ang artikulong ito na nakakainip, may depekto, o sa pangkalahatan ay hindi ang iyong tasa ng tsaa. Sa kasong ito, huwag mag-atubiling lumipat sa iba pang mga bagay o, kung bihasa ka sa paksa, mangyaring gumawa ng mga mungkahi sa mga komento kung paano ko mapapabuti o madagdagan ang artikulo. Isang paliwanag ng mga expression ng lambda sa Java.  Sa mga halimbawa at gawain.  Bahagi 2 - 1Ang materyal ay hindi inaangkin na may anumang akademikong halaga, pabayaan ang pagiging bago. Sa kabaligtaran: Susubukan kong ilarawan ang mga bagay na kumplikado (para sa ilang tao) nang simple hangga't maaari. Isang kahilingan na ipaliwanag ang Stream API ang nagbigay inspirasyon sa akin na isulat ito. Naisip ko ito at nagpasya na ang ilan sa aking mga halimbawa ng stream ay hindi mauunawaan nang walang pag-unawa sa mga expression ng lambda. Kaya magsisimula tayo sa mga expression ng lambda.

Pag-access sa mga panlabas na variable

Nag-compile ba ang code na ito sa isang hindi kilalang klase?

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

    @Override 
    public void run() { 
        counter++;
    }
};
Hindi. Ang counter variable ay dapat na final. O kung hindi final, hindi bababa sa hindi nito mababago ang halaga nito. Nalalapat ang parehong prinsipyo sa mga expression ng lambda. Maa-access nila ang lahat ng mga variable na maaari nilang "makita" mula sa lugar kung saan sila idineklara. Ngunit hindi dapat baguhin ng isang lambda ang mga ito (magtalaga ng bagong halaga sa kanila). Gayunpaman, mayroong isang paraan upang laktawan ang paghihigpit na ito sa mga hindi kilalang klase. Lumikha lamang ng isang reference na variable at baguhin ang panloob na estado ng object. Sa paggawa nito, ang variable mismo ay hindi nagbabago (tumuturo sa parehong bagay) at maaaring ligtas na mamarkahan bilang final.

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

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Narito ang aming countervariable ay isang sanggunian sa isang AtomicIntegerbagay. At ang incrementAndGet()pamamaraan ay ginagamit upang baguhin ang estado ng bagay na ito. Ang halaga ng variable mismo ay hindi nagbabago habang tumatakbo ang programa. Palagi itong tumuturo sa parehong bagay, na nagbibigay-daan sa amin na ideklara ang variable na may panghuling keyword. Narito ang parehong mga halimbawa, ngunit may mga expression ng lambda:

int counter = 0;
Runnable r = () -> counter++;
Hindi ito mag-compile para sa parehong dahilan tulad ng bersyon na may hindi kilalang klase:  counterhindi dapat magbago habang tumatakbo ang program. Ngunit ang lahat ay maayos kung gagawin natin ito tulad nito:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Nalalapat din ito sa mga paraan ng pagtawag. Sa loob ng mga expression ng lambda, hindi mo lamang maa-access ang lahat ng "nakikita" na mga variable, ngunit maaari ring tawagan ang anumang naa-access na mga pamamaraan.

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!");
    }
}
Bagama't staticMethod()pribado, ito ay naa-access sa loob ng main()pamamaraan, kaya maaari din itong tawagan mula sa loob ng isang lambda na nilikha sa mainpamamaraan.

Kailan isinasagawa ang isang lambda expression?

Maaari mong makitang masyadong simple ang sumusunod na tanong, ngunit dapat mong itanong ito nang pareho: kailan isasagawa ang code sa loob ng lambda expression? Kapag ito ay nilikha? O kapag ito ay tinawag (na hindi pa alam)? Ito ay medyo madaling suriin.

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(); 
Output ng screen:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Maaari mong makita na ang lambda expression ay naisakatuparan sa pinakadulo, pagkatapos malikha ang thread at kapag naabot lamang ng pagpapatupad ng programa ang run()pamamaraan. Tiyak na hindi kapag ito ay ipinahayag. Sa pamamagitan ng pagdedeklara ng lambda expression, nakagawa lang kami ng Runnableobject at inilarawan kung paano run()kumikilos ang pamamaraan nito. Ang pamamaraan mismo ay naisakatuparan sa ibang pagkakataon.

Mga sanggunian ng pamamaraan?

Ang mga sanggunian ng pamamaraan ay hindi direktang nauugnay sa mga lambdas, ngunit sa palagay ko ay makatuwirang magsabi ng ilang mga salita tungkol sa mga ito sa artikulong ito. Ipagpalagay na mayroon kaming lambda expression na hindi gumagawa ng anumang espesyal, ngunit tumatawag lamang ng isang paraan.

x -> System.out.println(x)
Nakatanggap ito ng ilan xat tumatawag lang System.out.println(), pumapasok x. Sa kasong ito, maaari naming palitan ito ng isang sanggunian sa nais na paraan. Ganito:

System.out::println
Tama — walang panaklong sa dulo! Narito ang isang mas kumpletong halimbawa:

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

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

strings.forEach(x -> System.out.println(x));
Sa huling linya, ginagamit namin ang forEach()pamamaraan, na kumukuha ng isang bagay na nagpapatupad ng Consumerinterface. Muli, ito ay isang functional na interface, na may isang void accept(T t)paraan lamang. Alinsunod dito, nagsusulat kami ng lambda expression na may isang parameter (dahil ito ay nai-type sa mismong interface, hindi namin tinukoy ang uri ng parameter; ipinapahiwatig lamang namin na tatawagin namin ito x). Sa katawan ng lambda expression, isinusulat namin ang code na isasagawa kapag accept()tinawag ang pamamaraan. Dito lang namin ipinapakita kung ano ang napunta sa xvariable. Ang parehong forEach()pamamaraan na ito ay umuulit sa lahat ng mga elemento sa koleksyon at tinatawag ang accept()pamamaraan sa pagpapatupad ngConsumerinterface (aming lambda), pagpasa sa bawat item sa koleksyon. Tulad ng sinabi ko, maaari nating palitan ang gayong lambda expression (isa na simpleng klase ng ibang paraan) na may sanggunian sa nais na pamamaraan. Pagkatapos ang aming code ay magiging ganito:

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

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

strings.forEach(System.out::println);
Ang pangunahing bagay ay ang mga parameter ng println()at accept()mga pamamaraan ay tumutugma. Dahil ang println()pamamaraan ay maaaring tumanggap ng anumang bagay (ito ay na-overload para sa lahat ng mga primitive na uri at lahat ng mga bagay), sa halip na mga lambda expression, maaari lamang tayong magpasa ng isang reference sa println()pamamaraan sa forEach(). Pagkatapos forEach()ay kukunin ang bawat elemento sa koleksyon at ipapasa ito nang direkta sa println()pamamaraan. Para sa sinumang makatagpo nito sa unang pagkakataon, pakitandaan na hindi kami tumatawag System.out.println()(na may mga tuldok sa pagitan ng mga salita at may panaklong sa dulo). Sa halip, nagpapasa kami ng sanggunian sa pamamaraang ito. Kung isusulat natin ito

strings.forEach(System.out.println());
magkakaroon tayo ng compilation error. Bago ang tawag sa forEach(), nakikita ng Java System.out.println()na tinatawag iyon, kaya nauunawaan nito na ang return value ay voidat susubukang ipasa voidsa forEach(), na sa halip ay umaasa sa isang Consumerbagay.

Syntax para sa mga sanggunian ng pamamaraan

Ito ay medyo simple:
  1. Nagpasa kami ng sanggunian sa isang static na pamamaraan tulad nito: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. Nagpapasa kami ng reference sa isang non-static na pamamaraan gamit ang isang umiiral na bagay, tulad nito: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. Nagpapasa kami ng sanggunian sa isang non-static na pamamaraan gamit ang klase na nagpapatupad nito tulad ng sumusunod: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. Nagpasa kami ng isang sanggunian sa isang tagabuo na tulad nito:ClassName::new

    Ang mga sanggunian ng pamamaraan ay napaka-maginhawa kapag mayroon ka nang pamamaraan na perpektong gagana bilang isang callback. Sa kasong ito, sa halip na magsulat ng lambda expression na naglalaman ng code ng method, o magsulat ng lambda expression na tinatawag lang ang method, nagpapasa lang kami ng reference dito. At ayun na nga.

Isang kawili-wiling pagkakaiba sa pagitan ng mga hindi kilalang klase at mga expression ng lambda

Sa isang hindi kilalang klase, ang thiskeyword ay tumuturo sa isang bagay ng hindi kilalang klase. Ngunit kung gagamitin natin ito sa loob ng isang lambda, makakakuha tayo ng access sa object ng naglalaman ng klase. Ang isa kung saan talaga namin sinulat ang lambda expression. Nangyayari ito dahil pinagsama-sama ang mga expression ng lambda sa isang pribadong paraan ng klase kung saan sila nakasulat. Hindi ko irerekomenda ang paggamit ng "feature" na ito, dahil mayroon itong side effect at sumasalungat sa mga prinsipyo ng functional programming. Iyon ay sinabi, ang diskarte na ito ay ganap na naaayon sa OOP. ;)

Saan ko nakuha ang aking impormasyon at ano pa ang dapat mong basahin?

At, siyempre, nakakita ako ng isang toneladang bagay sa Google :)
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION