Ismerkedjünk Lambda-kifejezésekkel!

Lambda-kifejezésA Java 8-tól használhatunk Lambda-kifejezéseket, amivel hatékonyabban, rövidebben és könnyebben valósíthatunk meg tipikus műveleteket.

Korábban általában az eseménykezelést globálisan (interfészek implementálásával), vagy lokálisan (névtelen interfész implementálásával) oldottuk meg, illetve besegítettek adapterek is. Sok- és többféle eseménynél ez a forráskódunk elaprózódásához vezetett, ami nehézkes olvashatóságot és karbantarthatóságot eredményezett.

A lambda-kifejezés egy olyan kódrészlet, amelyben valamihez hozzárendelünk valamit (x -> y), például egy metódus paraméteréhez a végrehajtandó forráskódot. Ehhez építünk a funkcionális interfészekre és a metódus referenciákra (szintén Java 8-tól), illetve a típus kikövetkeztetés képességére is (Java 7-től).

A kiválogatás programozási tételt valósítjuk meg többféle implementációval, felhasználva a Java nyelv újdonságait, és a fentieken kívül még a Stream API-t is.

Adatforrás

Először is kellenek adatok, hiszen azokat dolgozzuk fel. Egy Termek osztályú egyszerű POJO-val dolgozunk, nev és ar tulajdonságokkal, generált konstruktorral, getter metódusokkal és toString()-gel. Az adatforrást biztosító absztrakt Lista ősosztályban a POJO-kból felépítünk egy termekLista nevű generikus listát (például CSV vagy XML fájlból beolvasva az összetartozó adatokat) – listaFeltolt() eljárás – és implementálunk egy univerzálisan használható listaKiir(String uzenet, List termekLista) eljárást is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
abstract class Lista {
  private File xmlFajl=new File("./files/prod.xml");
  private File csvFajl=new File("./files/prod.csv");
  protected ArrayList termekLista=null;

  public Lista() {
    listaFeltolt();
    listaKiir("Összes termék listája", termekLista);
  }
 
  private void listaFeltolt() {
    …
  }

  public void listaKiir(String uzenet, List termekLista) {
    System.out.print(uzenet+" - ");
    if(termekLista.isEmpty()) {
      System.out.println("Nincs ilyen termék.\n");
      return;
    }
    System.out.println(termekLista.size()+" db:");
    termekLista.forEach(termek -> System.out.println(termek));
    System.out.println();
  }
}

 
Örökítünk három utódosztályt a Lista osztályból, amelyek különbözőképpen dolgozzák fel a termekLista-t, bemutatva a fejlődés útját, illetve a rendelkezésre álló lehetőségeket.

Válogassunk a termékek közül négyféleképpen és adjuk vissza azon termékeket, amelyek:

  • limit alatti áron kaphatók,
  • ára limit1 és limit2 közé esik (zárt intervallumban),
  • neve adott szöveggel kezdődik (kis- és nagybetű különbözik),
  • neve adott szöveget tartalmaz (kis- és nagybetű nem különbözik)!

1. változat

Hagyományos megközelítéssel a fentiek megvalósításához külön egy-egy függvény tartozik, ahogyan az alábbiakban látható:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Kivalogatas1 extends Lista {

  private List<Termek> termekListaLimitAr1(int limit) {
    List<Termek> lista=new ArrayList<>();
//    for (int i = 0; i < termekLista.size(); i++) //1
//      if(termekLista.get(i).getAr()<limit)
//        lista.add(lista.get(i));
////    Iterator i=termekLista.iterator(); //2
////    while(i.hasNext()) {
////      Termek termek=i.next();
////      if(termek.getAr()<limit)
////        lista.add(termek);
////    }
//////    for (Termek termek : termekLista) //3
//////      if(termek.getAr()<limit)
//////        lista.add(termek);
////////    termekLista.stream(). //4
////////      filter(termek -> termek.getAr()<limit).
////////      forEach(termek -> lista.add(termek));
    lista=termekLista.stream(). //5
      filter(termek -> termek.getAr()<limit).
      collect(Collectors.toList());
    return lista;
  }

  private List<Termek> termekListaLimitAr2(int limit1, int limit2) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> limit1<=termek.getAr() && termek.getAr()<=limit2).
      forEach(termek -> lista.add(termek));
    return lista;
  }
 
  private List<Termek> termekListaSzoveg1(String szoveg) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> termek.getNev().startsWith(szoveg)).
      forEach(termek -> lista.add(termek));
    return lista;
  }

  private List<Termek> termekListaSzoveg2(String szoveg) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> termek.getNev().toLowerCase().
        contains(szoveg.toLowerCase())).
      forEach(termek -> lista.add(termek));
    return lista;
  }

  public void run() {
    listaKiir("Terméklista - ár<10", termekListaLimitAr1(10));
    listaKiir("Terméklista - 10<=ár<=20", termekListaLimitAr2(10, 20));
    listaKiir("Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik)",
      termekListaSzoveg1("Sir"));
    listaKiir("Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik)",
      termekListaSzoveg2("hot"));
  }
}

 
A termekListaLimitAr1() függvényben látható ötféle lehetőség a kiválogatásra a termekLista-ból:

  • //1: hagyományos, index alapú változat,
  • //2: iterátorra közvetlenül építő változat,
  • //3: bejáró ciklus, iterátorra közvetve építő változat,
  • //4: Stream API-ra építő változat, kiválogatás lambda-kifejezéssel (filter), a megmaradó termékekre végrehajtandó forEach művelet lambda kifejezéssel,
  • //5: Stream API-ra építő változat, kiválogatás lambda-kifejezéssel (filter), a megmaradó termékeket összegyűjtő/leképező collect művelettel.

Jól megfigyelhető, hogy négy függvény vázszerkezete megegyezik, és csupán a filter-ben található lambda-kifejezések különböznek. Ez a megoldás meglehetősen redundáns, nem általánosítható, valamint további műveletek megvalósítása további függvények implementálását igényli.

2. változat

Őrizzük meg a négyféle funkciót, sőt tegyük lehetővé, hogy ez tetszőlegesen bővíthető legyen. Használjunk saját generikus, funkcionális Feltetel interfészt saját döntés megvalósítását biztosítani tudó implementálandó teszt() függvénnyel, az alábbiak szerint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface Feltetel<T> {
  boolean teszt(T t);  
}

class Kivalogatas2 extends Lista {
 
  private List<Termek> termekListaFeltetel(Feltetel<Termek> feltetel) {
    List<Termek> lista=new ArrayList<>();
//    for (Termek termek : termekLista)
//      if(feltetel.teszt(termek))
//        lista.add(termek);
    termekLista.stream().
      filter(termek -> feltetel.teszt(termek)).
      forEach(termek -> lista.add(termek));
    return lista;
  }
 
  public void run() {
    listaKiir("Terméklista - ár<10", termekListaFeltetel(new Feltetel<Termek>() {
      @Override
      public boolean teszt(Termek t) {
        return t.getAr()<10;
      }
    }));
//    listaKiir("Terméklista - ár<10", termekListaFeltetel((Termek termek) -> termek.getAr()<10));
    listaKiir("Terméklista - 10<=ár<=20",
      termekListaFeltetel((Termek termek) -> 10<=termek.getAr() && termek.getAr()<=20));
    listaKiir("Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().startsWith("Sir")));
    listaKiir("Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().toLowerCase().contains("hot".toLowerCase())));
  }
}

 
A termekListaFeltetel() függvény paramétere a saját Feltetel interfészünket implementáló névtelen osztály példánya, amely felhasználható:

  • //6: ciklusban egyszerű feltételként,
  • //7: Stream API filter műveletében megadott lambda-kifejezésben,
  • //8: a listaKiir() metódusban paraméterként átadva névtelen osztály példányaként,
  • //9-től: a listaKiir() metódusban paraméterként átadva lambda-kifejezésként.

Látszik, hogy többféle kiválogató művelethez egyetlen implementált függvény szükséges. Ez a megoldás már jóval általánosabb.

3. változat

A saját interfész helyett használjuk fel a beépített Predicate generikus, funkcionális interfészt, építve annak test() függvényére az alábbiak szerint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Kivalogatas3 extends Lista {
 
  private List<Termek> termekListaFeltetel(Predicate<Termek> feltetel) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> feltetel.test(termek)).
      forEach(termek -> lista.add(termek));
    return lista;
  }
 
  public void run() {
    listaKiir("Terméklista - ár<10", termekListaFeltetel(new Predicate<Termek>() {
      @Override
      public boolean test(Termek t) {
        return t.getAr()<10;
      }
    }));
    listaKiir("Terméklista - ár<10", termekListaFeltetel(
      (Termek termek) -> termek.getAr()<10));
    listaKiir("Terméklista - 10<=ár<=20",
      termekListaFeltetel((Termek termek) -> 10<=termek.getAr() && termek.getAr()<=20));
    listaKiir("Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().startsWith("Sir")));
    listaKiir("Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().toLowerCase().contains("hot".toLowerCase())));
  }
}

 

Belépési pont

Végül következzen a közös belépési pont, amelyben tetszőlegesen aktiválható és tesztelhető mindhárom változat működése:

1
2
3
4
5
6
7
public class Kivalogatas {
  public static void main(String[] args) {
    //new Kivalogatas1().run();
    //new Kivalogatas2().run();
    new Kivalogatas3().run();
  }
}

 

Mit ír ki a program a konzolra?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
Összes termék listája - 77 db:
Chai, 18.0
Chang, 19.0
Aniseed Syrup, 10.0
Chef Anton's Cajun Seasoning, 22.0
Chef Anton's Gumbo Mix, 21.35
Grandma's Boysenberry Spread, 25.0
Uncle Bob's Organic Dried Pears, 30.0
Northwoods Cranberry Sauce, 40.0
Mishi Kobe Niku, 97.0
Ikura, 31.0
Queso Cabrales, 21.0
Queso Manchego La Pastora, 38.0
Konbu, 6.0
Tofu, 23.25
Genen Shouyu, 15.5
Pavlova, 17.45
Alice Mutton, 39.0
Carnarvon Tigers, 62.5
Teatime Chocolate Biscuits, 9.2
Sir Rodney's Marmalade, 81.0
Sir Rodney's Scones, 10.0
Gustaf's Knäckebröd, 21.0
Tunnbröd, 9.0
Guaraná Fantástica, 4.5
NuNuCa Nuß-Nougat-Creme, 14.0
Gumbär Gummibärchen, 31.23
Schoggi Schokolade, 43.9
Rössle Sauerkraut, 45.6
Thüringer Rostbratwurst, 123.79
Nord-Ost Matjeshering, 25.89
Gorgonzola Telino, 12.5
Mascarpone Fabioli, 32.0
Geitost, 2.5
Sasquatch Ale, 14.0
Steeleye Stout, 18.0
Inlagd Sill, 19.0
Gravad lax, 26.0
Côte de Blaye, 263.5
Chartreuse verte, 18.0
Boston Crab Meat, 18.4
Jack's New England Clam Chowder, 9.65
Singaporean Hokkien Fried Mee, 14.0
Ipoh Coffee, 46.0
Gula Malacca, 19.45
Rogede sild, 9.5
Spegesild, 12.0
Zaanse koeken, 9.5
Chocolade, 12.75
Maxilaku, 20.0
Valkoinen suklaa, 16.25
Manjimup Dried Apples, 53.0
Filo Mix, 7.0
Perth Pasties, 32.8
Tourtiere, 7.45
Pâté chinois, 24.0
Gnocchi di nonna Alice, 38.0
Ravioli Angelo, 19.5
Escargots de Bourgogne, 13.25
Raclette Courdavault, 55.0
Camembert Pierrot, 34.0
Sirop d'érable, 28.5
Tarte au sucre, 49.3
Vegie-spread, 43.9
Wimmers gute Semmelknödel, 33.25
Louisiana Fiery Hot Pepper Sauce, 21.05
Louisiana Hot Spiced Okra, 17.0
Laughing Lumberjack Lager, 14.0
Scottish Longbreads, 12.5
Gudbrandsdalsost, 36.0
Outback Lager, 15.0
Flotemysost, 21.5
Mozzarella di Giovanni, 34.8
Röd Kaviar, 15.0
Longlife Tofu, 10.0
Rhönbräu Klosterbier, 7.75
Lakkalikööri, 18.0
Original Frankfurter grüne Soße, 13.0

Terméklista - ár<10 - 11 db:
Konbu, 6.0
Teatime Chocolate Biscuits, 9.2
Tunnbröd, 9.0
Guaraná Fantástica, 4.5
Geitost, 2.5
Jack's New England Clam Chowder, 9.65
Rogede sild, 9.5
Zaanse koeken, 9.5
Filo Mix, 7.0
Tourtiere, 7.45
Rhönbräu Klosterbier, 7.75

Terméklista - 10<=ár<=20 - 29 db:
Chai, 18.0
Chang, 19.0
Aniseed Syrup, 10.0
Genen Shouyu, 15.5
Pavlova, 17.45
Sir Rodney's Scones, 10.0
NuNuCa Nuß-Nougat-Creme, 14.0
Gorgonzola Telino, 12.5
Sasquatch Ale, 14.0
Steeleye Stout, 18.0
Inlagd Sill, 19.0
Chartreuse verte, 18.0
Boston Crab Meat, 18.4
Singaporean Hokkien Fried Mee, 14.0
Gula Malacca, 19.45
Spegesild, 12.0
Chocolade, 12.75
Maxilaku, 20.0
Valkoinen suklaa, 16.25
Ravioli Angelo, 19.5
Escargots de Bourgogne, 13.25
Louisiana Hot Spiced Okra, 17.0
Laughing Lumberjack Lager, 14.0
Scottish Longbreads, 12.5
Outback Lager, 15.0
Röd Kaviar, 15.0
Longlife Tofu, 10.0
Lakkalikööri, 18.0
Original Frankfurter grüne Soße, 13.0

Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik) - 3 db:
Sir Rodney's Marmalade, 81.0
Sir Rodney's Scones, 10.0
Sirop d'érable, 28.5

Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik) - 2 db:
Louisiana Fiery Hot Pepper Sauce, 21.05
Louisiana Hot Spiced Okra, 17.0

 
A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java SE szoftverfejlesztő tanfolyam szakmai moduljának 21-24. óra: Objektumorientált programozás 2. rész, 25-28. óra: Objektumorientált programozás 3. rész, valamint a Java EE szoftverfejlesztő tanfolyam szakmai moduljának 9-12. óra: XML feldolgozás alkalmaihoz kötődik.

Ismerkedjünk Lambda-kifejezésekkel!” bejegyzéshez 4 hozzászólás

Szólj hozzá!