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):

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


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:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


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á:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

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:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

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!

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

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.

 

www.informatika-programozas.hu - További információk!

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:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

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:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

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

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

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:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

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:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

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.