Gyakorlati alapok III.
A funkcionális interfész és a lambda-kifejezések
A funkcionális interfész az interfészek egy olyan speciális fajtája, amelyik csakis egyetlen, absztrakt funkcióval rendelkezik. Ilyen például a Runnable, amelyiknek tehát csupán egyetlen run() metódusa van és ha szándékunkban áll egy osztályba csatlakoztatni (implements Runnable), akkor azt kötelezően ki kell dolgoznunk. (Néhány további funkcionális interfész: Comparable, ActionListener).
Bevezetésképpen nézzünk meg egy egyszerű szállétrehozást (thread) névtelen osztály segítségével (a száltechnológiáról a későbbiekben A többszálú programozás alapjai (multithread) című fejezetben olvashatunk):

public class Szal implements Runnable {
public void run() {
for(int i = 1; i <= 10; i++) {
System.out.println(i + ". ciklus");
}
System.out.println("Kész!");
}
public static void main (String[] args) {
new Thread(new Szal()).start();
}
}
Végeredmény:
1. ciklus
2. ciklus
3. ciklus
4. ciklus
5. ciklus
6. ciklus
7. ciklus
8. ciklus
9. ciklus
10. ciklus
Kész!
Egy kissé másképpen:

class Szal {
public static void main(String args[]) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10; i++) {
System.out.println(i + ". ciklus");
}
System.out.println("Kész!");
}
}).start();
}
}
Végeredmény:
1. ciklus
2. ciklus
3. ciklus
4. ciklus
5. ciklus
6. ciklus
7. ciklus
8. ciklus
9. ciklus
10. ciklus
Kész!
Java 8 előtt a funkcionális interfészeket csakis a fenti trükkök bevetésével tudtuk munkára bírni. Az úgynevezett lambda-kifejezésekkel ez jóval egyszerűbbé vált. Induljunk ki a fenti példából és alakítsuk át ilyen módon futtathatóvá:

class Szal {
public static void main(String args[]) {
new Thread(() -> {
for(int i = 1; i <= 10; i++) {
System.out.println(i + ". ciklus");
}
System.out.println("Kész!");
}).start();
}
}
Végeredmény:
1. ciklus
2. ciklus
3. ciklus
4. ciklus
5. ciklus
6. ciklus
7. ciklus
8. ciklus
9. ciklus
10. ciklus
Kész!
A tapasztaltak észrevehetik a -> jelet, még pontosabban:
Objektum (paraméter) ->
Természetesen a környező kódok is egyaránt fontosak, de a jobbra mutató nyíl -hogy a funkciót végre alapszinten megértsük-, nagyjából ezt jelenti:
az objektumon (adott paraméterekkel) -> csináld ezt
Ellenőrizzük ezt le az alábbi, jóval egyszerűbb kódban:

interface
Udvozlet {
void kimond();
}
class Main {
public static void main(String []args) {
Udvozlet udv = () -> System.out.println("Hello World!");
udv.kimond();
}
}
Végeredmény:
Hello World!

A lambda-kifejezés első nagy előnye tehát, hogy szinte vilámgyorsan, mintegy "röptében" (on the fly) tudjuk az objektumon elvégezni a kívánt műveleteket.
Észrevehetjük, hogy az Udvozlet udv nem valós osztályból keletkezett, bár rendelkezik a legfőbb ősosztály, az Object összes, valamint az interfész dedikált metódusával.

Némelyik oktatási anyag azt állítja, hogy a keletkezett objektum nem tartozik egyetlen osztályhoz sem, ám ez nem igaz, hiszen belőle elérhetők az Object osztály metódusai.
A lambda-kifejezés további előnyeinek vizsgálatához először nézzük meg az alábbi, körtulajdonságokat számító kódot:

interface Kor
{
double szamitas(double sugar);
}
public class Main {
public static void main(String[] args) {
double r = 10;
Kor kor = (double sugar) -> 2 * sugar * Math.PI;
double korKerulet = kor.szamitas(r);
System.out.println("Korkerulet: " + korKerulet);
kor = (double sugar) -> Math.pow(sugar, 2) * Math.PI;
double korTerulet = kor.szamitas(r);
System.out.println("Korterulet: " + korTerulet);
}
}
Végeredmény:
Korkerulet: 62.83185307179586
Korterulet: 314.1592653589793
Kissé egyszerűbben:

interface Kor
{
double szamitas(double sugar);
}
public class Main {
public static void main(String[] args) {
double r = 10;
Kor kor = (double sugar) -> 2 * sugar * Math.PI;
System.out.println("Korkerulet: " + kor.szamitas(r));
kor = (double sugar) -> Math.pow(sugar, 2) * Math.PI;
System.out.println("KorTerulet: " + kor.szamitas(r));
}
}
Végeredmény:
Korkerulet: 62.83185307179586
Korterulet: 314.1592653589793

Láthatjuk, hogy a Kor objektumot csak egyszer kellett létrehoznunk, és bár funkcionális interfészről van szó egyetlen, absztrakt funkcióval, mégis metódusával együtt többször felhasználhatjuk!
Összehasonlításképpen nézzük meg az interfész "klasszikus" kidolgozását is:

interface Kor
{
double szamitas(double sugar);
}
public class Main implements Kor {
public static void main(String[] args) {
Main m = new Main();
double sugar = 10;
System.out.println("Korkerulet: " + m.szamitas(sugar));
}
@Override
public double szamitas(double sugar) {
return 2 * sugar * Math.PI;
}
}
Végeredmény:
Korkerulet: 62.83185307179586
Ezen változatban nem lehetséges még egy, azonos szignatúrájú metódust alkotnunk, következésképpen a létrehozott objektum metódusát (funkcionálisan) csak egyszer tudjuk felhasználni (itt double szamitas(double sugar)). A lambda-kifejezések használata tehát garantálja az ilyen jellegű többszöri felhasználhatóságot.
Levezetésképpen nézzük meg lambda kifejezéseinket, amikor egyszerre 2 paraméter bevonásával kényszerülnek működni:

interface Algebra {
double szamitas(double a, double b);
}
public class Main {
public static void main(String[] args) {
double elsoSzam = 5;
double masodikSzam = 3;
Algebra algebra = (double a, double b) -> a + b;
System.out.println("A 2 szam osszeadva: " +
algebra.szamitas(elsoSzam, masodikSzam));
algebra = (double a, double b) -> a - b;
System.out.println("A 2 szam kivonva: " +
algebra.szamitas(elsoSzam, masodikSzam));
algebra = (double a, double b) -> a * b;
System.out.println("A 2 szam szorozva: " +
algebra.szamitas(elsoSzam, masodikSzam));
algebra = (double a, double b) -> a / b;
System.out.println("A 2 szam osztva: " +
algebra.szamitas(elsoSzam, masodikSzam));
}
}
Végeredmény:
A 2 szam osszeadva: 8.0
A 2 szam kivonva: 2.0
A 2 szam szorozva: 15.0
A 2 szam osztva: 1.6666666666666667
A legjobb tudomásom szerint a lambda-kifejezések csakis funkcionális interfészek menedzselésekor használhatók fel. De mivel a Java 8 is már számos (43 db) beépített funkcionális interfészt kínál fel a java.util.function csomagban, illetve mi szintén alkothatunk ilyen interfészeket, a lambda-kifejezések használata fokozatosan egyre elterjedtebbé vált.

