- 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.
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 counter
variable ay isang sanggunian sa isang AtomicInteger
bagay. 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: counter
hindi 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 main
pamamaraan.
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 Runnable
object 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 x
at 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 Consumer
interface. 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 x
variable. Ang parehong forEach()
pamamaraan na ito ay umuulit sa lahat ng mga elemento sa koleksyon at tinatawag ang accept()
pamamaraan sa pagpapatupad ngConsumer
interface (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 void
at susubukang ipasa void
sa forEach()
, na sa halip ay umaasa sa isang Consumer
bagay.
Syntax para sa mga sanggunian ng pamamaraan
Ito ay medyo simple:-
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 } }
-
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 } }
-
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); } } }
-
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, angthis
keyword 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?
- Tutorial sa opisyal na website ng Oracle. Maraming detalyadong impormasyon, kabilang ang mga halimbawa.
- Kabanata tungkol sa mga sanggunian ng pamamaraan sa parehong tutorial sa Oracle.
- Kumuha ng sipsip sa Wikipedia kung ikaw ay tunay na mausisa.
GO TO FULL VERSION