Java 8 Lambda izrazi - duboko i razumljivo
Danasnja tema je „Uvod u Lambda izraze”, koju su neki citaoci ranije u komentarima energicno trazili da obradim. Izvinjavam se što ste cekali — evo, ispunjavam zelju, nije kasno, zar ne?

01. Prvo susret sa Lambda
Lambda izraz opisuje blok koda (odnosno anonimnu metodu) koji se moze proslediti kao parametar konstruktoru ili obicnoj metodi radi naknadnog izvrsenja. Razmotri sledeci kod:
() -> System.out.println("Chenmo Wang Er")Objasnim ga s leva na desno: () je lista parametara Lambda izraza (u ovom primeru bez parametara), -> oznacava da je ovaj kod Lambda izraz (odnosno, kad vidis ->, znas da je to Lambda), a System.out.println("Chenmo Wang Er") je kod koji se izvrsava — ispisuje „Chenmo Wang Er” na standardni izlaz.
Ko od vas koji ima osnove Jave verovatno nije stran sa interfejsom Runnable — to je jedan od osnovnih interfejsa za visenitno programiranje; definisan je ovako:
@FunctionalInterface
public interface Runnable
{
public abstract void run();
}Interfejs Runnable je veoma jednostavan, sa samo jednom apstraktnom metodom run(); pažljivi citaoci primetice i nepoznatu anotaciju @FunctionalInterface — šta ona znaci?
Pogledah njegov izvorni kod; unutra se nalazi ovakav komentar:
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
U sustini, interfejsi oznaceni sa @FunctionalInterface mogu se instancirati pomocu Lambda izraza. Kako to izgleda u praksi?
Pre smo nit kreirali i pokretali ovako:
public class LamadaTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Chenmo Wang Er");
}
}).start();
}
}A pomocu Lambda izraza? Dovoljno je ovo:
public class LamadaTest {
public static void main(String[] args) {
new Thread(() -> System.out.println("Chenmo Wang Er")).start();
}
}Nije li divno! U poredjenju sa anonimnom unutrasnjom klasom, Lambda izraz je ne samo laksi za razumevanje, vec drastno skracuje kolicinu koda koji se mora napisati.

02. Lambda sintaksa
Svaki Lambda izraz postuje sledece pravilo:
( parameter-list ) -> { expression-or-statements }parameter-list unutar () je lista parametara razdvojenih zarezima. Mozes eksplicitno navesti tip parametra, a mozes i ne (kompajler ce ga zakljuciti iz konteksta). Od Jave 11, kao tip parametra moze se koristiti i kljucna rec var — pomalo podseca na JavaScript.
-> je identifikator Lambda izraza, poput carevog edikta — kad ga vidis, kao da vidis cara.
expression-or-statements unutar {} je telo Lambda izraza; moze biti jedna linija, a moze ih biti i vise.
Pomocu Lambda izraza moze se uraditi mnogo toga, na primer:
- Dodela vrednosti promenljivoj — primer:
Runnable r = () -> { System.out.println("Chenmo Wang Er"); };
r.run();- Kao rezultat
return— primer:
static FileFilter getFilter(String ext)
{
return (pathname) -> pathname.toString().endsWith(ext);
}- Kao element niza — primer:
final PathMatcher matchers[] =
{
(path) -> path.toString().endsWith("txt"),
(path) -> path.toString().endsWith("java")
};- Kao parametar obicne metode ili konstruktora — primer:
new Thread(() -> System.out.println("Chenmo Wang Er")).start();Treba obratiti paznju na opseg vazenja promenljivih unutar Lambda izraza.
public static void main(String[] args) {
int limit = 10;
Runnable r = () -> {
int limit = 5;
for (int i = 0; i < limit; i++)
System.out.println(i);
};
}Gornji kod ce pri kompajliranju prijaviti gresku: promenljiva limit je vec definisana.
Kao i kod anonimnih unutrasnjih klasa, u telu Lambda izraza ne treba menjati lokalne promenljive metode, u suprotnom kompajliranje nece proci: promenljive koriscene u Lambda izrazu moraju biti final.

Uzrok ovog problema je u tome što Java specifikacija kaze:
Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.
U sustini, promenljive koje se koriste u Lambda izrazu, a nisu deklarisane unutar tog izraza, moraju biti deklarisane kao final ili „effectively final”, inace ce doci do greske pri kompajliranju.
Moguce da neki citaoci ne znaju razliku izmedju final i „effectively final”, pa da dodam par reci.
final int a;
a = 1;
// a = 2;
// buduci da je a final, ne moze mu se ponovo dodeliti vrednost
int b;
b = 1;
// b se nakon toga vise ne menja
// b je effectively final
int c;
c = 1;
// c je prvo dobio vrednost 1, a zatim mu je dodeljena vrednost 2
c = 2;
// c nije effectively finalPošto smo razjasnili razliku izmedju final i „effectively final”, postaje jasno da ako se limit deklrise kao final, nece moci da mu se menja vrednost unutar Lambda izraza. Postoji li neko dobro resenje — da kompajler ne prijavljuje upozorenje, a da promenljiva moze da se menja?
Razmisljao sam i isprobavao, i konacno sam pronasao 3 izvodljiva resenja:
Deklarisati promenljivu
limitkaostatic.Deklarisati promenljivu
limitkaoAtomicInteger.Koristiti niz.
Hajde da ih jednog po jednog detaljno obradimo.
1) Deklaracija limit kao static
Da bi se limit deklarisao kao static, on se mora nalaziti van metode main(), jer je i sama main() metoda static. Kompletan primer koda je ispod.
public class ModifyVariable2StaticInsideLambda {
static int limit = 10;
public static void main(String[] args) {
Runnable r = () -> {
limit = 5;
for (int i = 0; i < limit; i++) {
System.out.println(i);
}
};
new Thread(r).start();
}
}Izlaz programa:
0
1
2
3
4OK, ovo resenje radi.
2) Deklaracija limit kao AtomicInteger
AtomicInteger osigurava da izmena int vrednosti bude atomska; moze se koristiti metoda set() za postavljanje nove int vrednosti i get() za preuzimanje trenutne int vrednosti.
public class ModifyVariable2AtomicInsideLambda {
public static void main(String[] args) {
final AtomicInteger limit = new AtomicInteger(10);
Runnable r = () -> {
limit.set(5);
for (int i = 0; i < limit.get(); i++) {
System.out.println(i);
}
};
new Thread(r).start();
}
}Izlaz programa:
0
1
2
3
4OK, i ovo resenje radi.
3) Koriscenje niza
Pristup preko niza nosi malo varalice: niz se u deklaraciji oznacava kao final, ali se pri promeni int vrednosti zapravo menja samo element tog niza.
public class ModifyVariable2ArrayInsideLambda {
public static void main(String[] args) {
final int [] limits = {10};
Runnable r = () -> {
limits[0] = 5;
for (int i = 0; i < limits[0]; i++) {
System.out.println(i);
}
};
new Thread(r).start();
}
}Izlaz programa:
0
1
2
3
4OK, i ovo resenje radi.
03. Lambda i kljucna rec this
Lambda izraz ne uvodi novi opseg — u tome se razlikuje od anonimne unutrasnje klase. Drugim recima, kljucna rec this unutar tela Lambda izraza ista je kao this instance klase u kojoj se nalazi.
Pogledaj sledeci primer.
public class LamadaTest {
public static void main(String[] args) {
new LamadaTest().work();
}
public void work() {
System.out.printf("this = %s%n", this);
Runnable r = new Runnable()
{
@Override
public void run()
{
System.out.printf("this = %s%n", this);
}
};
new Thread(r).start();
new Thread(() -> System.out.printf("this = %s%n", this)).start();
}
}Tips: %s znaci da se na toj poziciji stampa string, a %n je novi red (moze i \n, ali je %n cross-platformski).
Kod u metodi work() moze se podeliti u 3 dela:
- Samostalna kljucna rec
this
System.out.printf("this = %s%n", this);Ovde se this odnosi na objekat klase LamadaTest kreiran pomocu kljucne reci new u metodi main() — new LamadaTest().
- Kljucna rec
thisunutar anonimne unutrasnje klase
Runnable r = new Runnable()
{
@Override
public void run()
{
System.out.printf("this = %s%n", this);
}
};Ovde se this odnosi na objekat Runnable kreiran pomocu kljucne reci new u metodi work() — new Runnable(){...}.
- Kljucna rec
thisunutar Lambda izraza
Ovde je kljucna rec this ista kao u tacki 1).
Pogledajmo izlaz programa:
this = com.cmower.java_demo.journal.LamadaTest@3feba861
this = com.cmower.java_demo.journal.LamadaTest$1@64f033cb
this = com.cmower.java_demo.journal.LamadaTest@3feba861U skladu je sa nasom analizom.

04. Na kraju
Iako su Lambda izrazi na mnogo nacina iznenadjujuci koliko su pojednostavili Java programiranje, u odredjenim slucajevima neadekvatna upotreba i dalje moze dovesti do nepotrebnih nesporazuma — koristite ih obazrivo.
To je to, drago moj citaocu — to je ceo sadrzaj ovog clanka. Pozdravljam to što u ovim nesigurnim vremenima i dalje citate tehnicke tekstove, pa ti Erge mora podici palac gore. Originalan rad nije lakov; ako ti se cinio makar malo korisnim, nemoj stedeti pravo na lajk — jer je to moj najjaci pokretac za pisanje.
