A 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, például egy metódus paraméteréhez a végrehajtandó forráskódot ( x -> y). 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 termekListaLimitAr1(int limit) { //limit ár alatti termékek listája List 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 termekListaLimitAr2(int limit1, int limit2) { //limit1<=ár<=limit2 termékek listája List lista=new ArrayList<>(); termekLista.stream(). filter(termek -> limit1<=termek.getAr() && termek.getAr()<=limit2). forEach(termek -> lista.add(termek)); return lista; } private List termekListaSzoveg1(String szoveg) { //szoveg-gel kezdődő terméknevek listája (kis- és nagybetű különbözik) List lista=new ArrayList<>(); termekLista.stream(). filter(termek -> termek.getNev().startsWith(szoveg)). forEach(termek -> lista.add(termek)); return lista; } private List termekListaSzoveg2(String szoveg) { //szoveg-et tartalmazó terméknevek listája (kis- és nagybetű nem különbözik) List 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 34 |
class Kivalogatas2 extends Lista { private List termekListaFeltetel(Feltetel feltetel) { //feltetel-t teljesítő termékek listája List 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() { @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 28 29 30 31 32 |
class Kivalogatas3 extends Lista { private List termekListaFeltetel(Predicate feltetel) { //feltetel-t teljesítő termékek listája List 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() { @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 |
Összes termék listája - 77 db: Chai, 18.0 Chang, 19.0 Aniseed Syrup, 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 - á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.
Máskor is blogolunk a témakörben: Stream API lambda kifejezésekkel.
Péter is blogolt a témában: Stream API lambda kifejezésekkel
Ha összehasonlítanád a Java Lambda-kifejezéseit a C#-os lehetőségekkel:
https://dtk.tankonyvtar.hu/bitstream/handle/123456789/3629/2011-0038_50_biro_kovasznai_en.pdf?sequence=1&isAllowed=y, 15. fejezet
Köszönjük István. Feldolgozzuk majd a kapott ötleteket.
Sziasztok, köszi: hasznos volt zh közben.
Örülünk neki Anita. 😉
A témához ajánlom a Java 11 újdonságai közül (https://www.baeldung.com/java-11-new-features) ezt a kettőt. Hasznos tartom:
–
Predicate.not()
művelet– kollekció tömbben támogatást, így:
String[] sampleArray = sampleList.toArray(String[]::new);
Kösz Róbert, egyetértek.