Stream API lambda kifejezésekkel

lambda kifejezés logo

lambda kifejezés logoKorábban blogoltunk már a Stream API-ról és a lambda kifejezésekről: Ismerkedjünk lambda kifejezésekkel! Most másképpen közelítve újra foglalkozunk a témával.

Tanfolyamainkon szinte minden adatszerkezethez, tömbhöz, kollekcióhoz, fájlkezeléshez kötődő témakörben használjuk mindkettőt. Áttekintjük az ezekhez szükséges minimális verziószámot, a szintaktika fejlődését, az együttes használat elvi és gyakorlati lehetőségeit. A szükséges alapfogalmakat definiáljuk: hozzárendelési szabály, funkcionális interfész, metódus referencia, alapértelmezett metódusok, típus kikövetkeztetés képessége, generikus és funkcionális programozás. párhuzamos adatfeldolgozás lehetőségei.

Összehasonlításokat is végzünk: a lambda előtti verziók lehetőségei, korlátai, tipikus lambda hibák, mikor mit érdemes és mit nem érdemes használni, paraméterek típusait megadjuk vagy elhagyjuk, hagyományos kollekciós műveletek (azért a generikusság előtti időkre már nem térünk ki) és folyam feldolgozás (adatforrás meghatározása, közbenső és végső műveletek).

Most azokat a Stream API-hoz és lambda kifejezésekhez kötődő bevezető mintapéldákat ismertetjük, amiket részletesen elemzünk tanfolyamaink szakmai moduljának kontakt óráin. Ezek közül közösen meg is írunk néhányat, kombinálunk is néhányat egy-egy összetett adatfeldolgozó művelet megvalósítása során. Programozási tételenként specifikáljuk a feladatokat és megmutatunk néhány megoldást.

1. Adatforrás

100 db olyan véletlen kétjegyű számot állítunk elő generikus listában, amelyek között biztosan előfordul legalább egyszer a 80.

2. Elemi programozási tételek

2.1. Sorozatszámítás

Kiírjuk, hogy mennyi a listában lévő számok összege:

2.2. Eldöntés

Két kérdésre adunk választ. Van-e a listában lévő számok között 35 (konkrét elem), illetve páros (adott tulajdonságú elem)?

2.3. Kiválasztás

Kiírjuk, hogy a biztosan előforduló (legalább 1 db közül balról az első) 80, hányadik helyen (index) található meg:

2.4. Keresés

Keressük a 35-öt az eldöntés és a kiválasztás összeépítésével:

2.5. Megszámolás

Kiírjuk, hogy hány db öttel osztható szám (adott tulajdonságú elem) található a listában:

2.6. Szélsőérték-kiválasztás

Kiírjuk a listában lévő legkisebb számot (értéket, nem indexet):

3. Összetett programozási tételek

3.1. Másolás

Készítünk egy másolatot a lista elemeiről (közben esetleg mindegyiket meg is változtathatjuk):

3.2. Kiválogatás

A listában lévő számok közül kiválogatjuk az öttel osztható számokat:

3.3. Szétválogatás

Külön-külön szétválogatjuk a listában lévő páros és páratlan számokat:

3.4. Unió

A korábban szétválogatott páros és páratlan számokat tartalmazó halmazok unióját állítjuk elő:

3.5. Metszet

A korábban szétválogatott páros és páratlan számokat tartalmazó halmazok metszetét állítjuk elő:

3.6. Összefésülés

A korábban szétválogatott páros és páratlan számokat összefésüljük:

4. A program eredménye a konzolon

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, a Java EE szoftverfejlesztő tanfolyam és a Java adatbázis-kezelő tanfolyam szakmai moduljának több alkalmához és az orientáló moduljának 1-4. óra: Programozási tételek alkalmához is kötődik. A Stream API-val és a lambda kifejezésekkel sokszor foglalkozunk.

Korábban is blogoltunk már a Stream API-ról és a lambda kifejezésekről: Ismerkedjünk lambda kifejezésekkel!

Kígyókocka grafikus felületen

Kígyókocka

KígyókockaA JavaFX grafikus felhasználói felületének és eseménykezelésének megvalósítása némileg eltér más Java GUI implementációk működésétől, például swing vagy Java3D. Főként animációk során hasznos használni. Megközelítése természetesen objektumorientált: a térbeli objektumok koordinátái, viselkedésük, transzformációkkal valósul meg, és azok is objektumok. A korábban elkészített konzolos kígyókocka programot valósítjuk meg most JavaFX GUI-val.

Ez egy két részből álló blog bejegyzés 2. része. A blog bejegyzés 1. része itt található.

A program működése

Kígyókocka JavaFX grafikus felületen

A program megvalósítása

A start() JavaFX életciklust indító eljárás felépíti a createGridUI() függvényt meghívva a felhasználói felületet (színpad/jelenet JavaFX-ben), beállítva a méreteket, címsort, és meghívja az eseménykezelésért felelős handleRotateButtons() eljárást.

A createGridUI() függvény a grafikus felhasználói felület elemeit paraméterezi (szerepe szerint Factory metódus). Öt elemből álló rács ( GridPane osztályú grid nevű objektum) készül el, amelyre nyilakat tartalmazó nyomógombok (pl.: Button típusú btLeft objektum) kerülnek fel a négy égtájnak megfelelően, valamint rajta középen helyezkedik el a kígyókocka 3D megjelenítését megvalósító objektum. A nyilak entitásai az Unikód karaktertáblából származnak. A kígyókocka objektumot a meghívott createSnakeCube() függvény hozza létre. A Node osztályú snakeCube nevű objektum geometriai transzformációs objektumot is hozzá kell rendelni: ez most a négyirányú forgatást megvalósítani képes névtelen Rotate osztályú objektum lesz. A forgatást 5 paraméterrel célszerű beállítani (van rá megfelelő túlterhelt konstruktor), ezek rendre: szög, X, Y, Z tengely origója és a forgatás tengelye. Az objektumok tulajdonosi hierarchiája swing-es szemmel nézve szokatlannak tűnik, de szemléletben legalább azonos a Java3D és a JavaFX megvalósítás.

A createSnakeCube() függvény előállítja a színpadra/jelenetbe kikerülő kígyókockát Node osztályú objektumként. A konstans CUBE tömb egységvektor rendszerben tartalmazza a kígyót alkotó kockák középpontjait. Az első ciklus mindezt nagyítást alkalmazva skálázza. A második ciklus koordináta és pont transzformációk alkalmazásával ( moveToMidPoint: eltolás középre, rotateAroundCenter: forgatás a középpont körül) a kiinduló állapotnak megfelelő méretben és pozícióban elhelyezi a kígyó útvonalát mutató hengerobjektumokat. A konstrukciós és transzformációs műveletek esetén alkalmazkodni kell ahhoz, hogy a JavaFX koordinátarendszerben az X jobbra, az Y lefelé, a Z pedig befelé (a nézőponttól távolodva a térben) növekszik. A matematikai hátteret részletesen most nem magyarázzuk el.

A handleRotateButtons() eljárás a forgatás 4 nyíl eseménykezelésének hozzárendelést végzi el. A nyomógomb objektumok setOnAction() hozzárendelő metódusának paramétere EventHandler funkcionális interfésszel és lambda művelettel működik. A forgatás irányát hozzárendeljük a megfelelő nyomógombhoz. Ez még csak végrendelkezés a jövőre: csak definiáljuk, hogy minek kell majd történnie, ha bekövetkezik az esemény (valamelyik nyílra/nyomógombra kattint a felhasználó).

A rotateSnake() eljárás minden nyíl feliratú nyomógombra kattintva reagál a bekövetkezett eseményre. A rotateAxis objektum a forgatás tengelye, a paraméterként átvett direction enum-tól függ, szinkronban azzal a nyomógombbal, amelyikre kattintott a felhasználó.

Ötletek a továbbfejlesztésre

  • Lehetne többféle irány is, például a négy sarokba átlós vagy mélységi irányú elforgatással.
  • Beépülhetne többféle transzformáció is, például skálázás (kicsinyítés, nagyítás), eltolás (közelítés, távolítás).
  • A kígyó útvonalát mutató hengerobjektumok kirajzolásának sorrendjén lehetne változtatni, mert a megjelenítés nem tökéletes. Jelenleg néhány helyzetben lehetetlennek, Escher lehetetlen konstrukcióihoz hasonlónak tűnhet a kígyókocka. Ha a tér mélységéből a nézőpont felé közeledve rajzolnánk ki a hengerobjektumokat, akkor a 3D látvány nem sérülne.

Tanfolyamainkon JavaFX grafikus felülettel hangsúlyosan nem foglalkozunk, de egy-egy kész forráskódot közösen megbeszélünk, és össze is hasonlítjuk a swing-es változattal. Fejlesztünk játékprogramot, de inkább konzolosan, vagy swing-es ablakban, vagy webes alkalmazásként.

A grafikus felületek felépítésének megismerése fontos lépcső az objektumorientált programozás elmélyítéséhez, gyakorlásához. A grafikus felületekhez egy másik lényeges szemléletváltás is kapcsolódik, hiszen a korábbi algoritmusvezérelt megközelítést felváltja az eseményvezérelt (eseménykezelés).

Tudatosan hangsúlyozott MVC-s projektben megoldva a feladatot, a modell rétegben tárolhatnánk többféle kígyókocka megjelenítéséhez és animációjához szükséges adatszerkezetet és transzformációs objektumokat/metódusokat is és a nézet/vezérlő rétegekben biztosíthatnánk ezek közül a kijelölést/kiválasztást menüvel, ikonokkal, eszköztárral, gyorsbillentyűkkel.

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

Tanfolyamaink orientáló moduljának 9-12. óra: Mesterséges intelligencia alkalmához kapcsolódóan a kígyókocka véletlenszerű előállítása helyett stratégiával rendelkező visszalépéses algoritmust specifikálhatunk és implementálhatunk.

Ez egy két részből álló blog bejegyzés 2. része. A blog bejegyzés 1. része itt található.

Ismerkedjünk lambda kifejezésekkel!

lambda kifejezés logo

lambda kifejezés logoA 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.

Ö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ó:

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:

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:

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:

Mit ír ki a program a konzolra?

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.