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ába 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!

Gyűjtsünk össze adatokat névjegykártya készítéshez!

névjegy

Induljunk ki az Oracle HR sémából!

Az EMPLOYEES táblából szükséges adatok: alkalmazottak neve konkatenálva a FIRST_NAME és LAST_NAME mezőkből, illetve a meglévő elérhetőségek: EMAIL (kiegészítve), PHONE_NUMBER és a hozzáadott WEBSITE. A JOBS táblából szükséges a munkakör megnevezése a JOB_TITLE mezőből, és a részleg neve a DEPARTMENTS tábla DEPARTMENT_NAME mezőből.

Építeni kell a DEPARTMENTS és EMPLOYEES táblák közötti 1:N kapcsolatra (azaz egy adott részlegben több alkalmazott is dolgozik), amelyet a DEPARTMENT_ID mező valósít meg. Nem szükséges az EMPLOYEES és DEPARTMENTS táblák közötti 1:N kapcsolat (azaz egy adott alkalmazott vezetőként több részleget is vezethet). Szükséges a JOBS és az EMPLOYEES táblák közötti 1:N kapcsolat, ami a JOB_ID mezővel valósul meg.

Hasznosak a köztes/átmeneti elnevezések a tábláknál ( D, J, E) és a mezőknél (például EMPLOYEE_NAME) egyaránt. Előbbieknél a mezőnevek minősítéséhez és egyértelmű hivatkozásaihoz kellenek, utóbbinál a metaadatokba kerülnek és utólag kiolvashatók ( ResultSetMetaData) és megjelenítéstől függően tartozhatnak például egy JTable vizuális komponens mögötti DefaultTableModel-hez.

A CONCAT függvénynek két paramétere lehet, ezért csak ott használtam, ahol ez kézenfekvő volt és elegendőnek bizonyult (az EMPLOYEE_NAME-nél nem akartam egymásba ágyazni két CONCAT-ot).

Az első lekérdezés a 107-ből 106 alkalmazott adatait adja vissza.
A második lekérdezés a hiányzó 1 alkalmazott adatai miatt szükséges, akinek nincs beállított részlege ( DEPARTMENT_ID IS NULL). Neki hiányos a COMPANY_DEPARTMENT_NAME adata, de így is egységes lehet az eredménytáblaként kapott adathalmaz (például oszlopok sorrendje és adattípusa).
A két lekérdezés eredményét egyesíteni kell ( UNION).

A lekérdező parancs

A lekérdező utasítást bele kell építeni egy Java kliensprogramba (MVC architekturális tervezési minta szerint a modell rétegbe), ami JDBC alapon kapcsolódik az Oracle adatbázis-szerver HR sémájához olyan felhasználó nevében, aki csatlakozhat és lekérdezhet. Meg kell tervezni és felügyelni kell a biztonságos kapcsolatot (kivételkezeléssel), annak életciklusát (nyit, lekérdez, zár), valamint gondoskodni kell az eredménytábla megjelenítéséről.

A keletkező eredménytábla exportálható Excel-be (XLS, XLSX formátumokba), és kiegészíthető például még egy oszloppal/mezővel (darabszám). Ezután átadható a grafikusnak, aki például felhasználja azt adatforrásként saját névjegykártya tervező szoftverében, vagy használja a Word körlevél varázslóját. Az adatforrás sorrendje ( ORDER BY) megkönnyítheti az elkészült névjegykártyák szétosztását.

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 45-48. óra: Adatbázis-kezelés JDBC alapon, 1. rész alkalmához, illetve a Java adatbázis-kezelő tanfolyam 9-12. óra: Oracle HR séma elemzése, 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalomhoz kapcsolódik.

Az SQL forráskód formázásához a Free Online SQL Formatter-t használtam.

Fát építünk

Fát építünk

Fát építünkAz adatok strukturális és könnyen értelmezhető formában való megjelenítése egy szoftver felhasználói felületén átgondolt tervezést igényel. Az adatokhoz hozzá kell jutni, ki kell választani a megfelelő grafikus komponenst, a mögötte lévő adatmodellt, össze kell ezeket kötni. Gyakran előforduló feladat, hogy táblázatosan is ábrázolható adatokból – felhasználva az adatok közötti összefüggéseket és kapcsolatokat – csoportosítva jelenítsünk meg hierarchikusan, fa struktúrában, kinyitható-becsukható formában, ahogyan ezt a felhasználók jól ismerik a fájl- és menürendszereket használva.

Fát építünk kétféleképpen

Adatbázisból, az Oracle HR sémából lekérdezünk két összetartozó nevet: részleg és alkalmazott. A lekérdezés során figyelünk a megfelelő sorrendre, ami a későbbi feldolgozást megkönnyíti. Adatainkat részlegnév szerint növekvő, azon belül alkalmazott neve szerint is növekvő – ábécé szerinti – sorrendbe rendezzük. A vezérlő rétegben két függvényt írunk, amely a modell rétegtől jut hozzá az adatokat tartalmazó generikus listához – átvett paraméterként –, és a visszaadott érték a nézet réteghez kerül.

A csoportváltás algoritmust használjuk, amely 5 blokkból épül fel. A külső ciklus előtti 1. blokk és utáni 5. blokk egyszer hajtódik végre, az előkészítő és lezáró tevékenységek tartoznak ide. A külső ciklus elején és végén található 2. és 4. blokk a belső cikluson kívül fut le, csoportonként, kategóriánként, részlegenként egyszer (most összesen 11-szer mindkettő). A 3. blokk a belső cikluson belül található, és alkalmazottanként egyszer hajtódik végre (most összesen 106-szor).

Háromszintű fát építünk: a gyökérbe (0. szint) fix, beégetett szövegként kerül a cég neve és a teljes létszám. Az 1. szinten jelennek meg a részlegek nevei és a hozzájuk tartozó létszámok. A 2. szint az alkalmazottak neveiből áll.

1. megoldás

A megoldás faKeszit1() függvénye szöveges adatot eredményez. Ez jól használható teszteléshez: megvan-e az összes adat, megfelelő-e a részlegek sorrendje azon belül az alkalmazottak sorrendje, működik-e a csoportosítás, rendben van-e a megszámolás?

A faKeszit1() függvény egy sok lépésben összefűzött (konkatenált) szöveget ad vissza. Az 1. blokkban előkészítjük a fa gyökerét, ami StringBuilder típusú, hiszen sokszor manipuláljuk és inicializáljuk a lista indexelésére használt i ciklusváltozót. A 2. blokkban megjegyezzük az aktuális részleget és előkészítjük az ehhez tartozó alkalmazottak nevét tároló generikus listát ( faReszlegAlkalmazott). Az aktReszleg-hez tartozó alkalmazottak neveit összegyűjtjük a 3. blokkban. Egy részleg feldolgozását a 4. blokkban fejezzük be a fa aktuális 1. és 2. szinten lévő elemeinek szövegbe való beszúrásával. A belső ciklushoz kötődően megszámolást nem kell alkalmaznunk, hiszen az adott részlegben dolgozó alkalmazottak száma a generikus listától elkérhető ( size()). Építünk arra, hogy a külső ciklusból nézve az egymás után végrehajtódó 2. és 4. blokkban az aktReszleg nem változik meg. A 2. blokkban még nem tudjuk a fa aktuális 1. szintjét hozzáfűzni a szöveghez, hiszen a létszám csak a belső ciklusban felépülő kollekciótól kérhető el utólag. Szükséges némi késleltetés, hiszen a szöveg összefűzése és lényegesen egyszerűbb (mint utólag manipulálni megfelelő helyeken). Az 5. blokkban a csoportváltás algoritmushoz kötődő tevékenységünk nincs.

Az 1. megoldás eredménye

2. megoldás

A faKeszit2() függvénynél alkalmazkodunk ahhoz, hogy a JTree vizuális komponenshez DefaultTreeModel observable típusú modell szükséges, így ezzel térünk vissza ( faModell). A fa csomópontjai DefaultMutableTreeNode osztályú objektumok lesznek, amelyeknek a userObject tulajdonsága szükség esetén manipulálható. Az 1 blokkban beszúrjuk a fa gyökerét ( faGyoker), amihez a későbbiekben csatlakozik a fa többi eleme. A 2. blokkban megjegyezzük az aktuális részleget és előkészítjük – megjelenítendő szöveg nélkül – a faReszleg csomópontot. A 3. blokkban fabeli csomópontként a fa 1. szintjén megjelenő részleghez névtelenül hozzáadjuk a fa 2. szintjére kerülő – aktuális részleghez tartozó – alkalmazottak nevét. A 4. blokkban utólag módosítjuk a faReszleg csomópont megjelenítendő szövegét. Az aktuális részleg létszámát itt sem kell külön megszámolni, mert a faReszleg-től elkérhető ( getChildCount()). Az 5. blokkban itt sincs különösebb teendőnk.

A 2. megoldás eredménye

Fát építünk, képernyőkép

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

Attól függően, hogyan jutunk hozzá a megjelenítéshez szükséges adatokhoz, több tanfolyamunkhoz is kapcsolódik a feladat és a modell rétegben mindig másképpen tervezünk és implementálunk:

  • A Java SE szoftverfejlesztő tanfolyam 45-48. óra: Adatbázis-kezelés JDBC alapon, 1. rész alkalmán hagyományos SQL lekérdező utasítást készítünk JDBC környezetben.
  • A Java EE szoftverfejlesztő tanfolyam 25-32. óra: Adatbázis-kezelés JPA alapon alkalommal a perzisztencia szolgáltatásait vetjük be.
  • A Java adatbázis-kezelő tanfolyam 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalmain hierarchikus lekérdezéseket használunk.

Kik vettek részt projektmunkában?

Projektmunka

ProjektmunkaHasonlítsuk össze a részlegeket fókuszálva arra, hogy az alkalmazottak mennyire vettek korábban részt projektmunkákban! Hányan igen és hányan nem? Van(nak) olyan részleg(ek), amelyik vezetője egyetlen alkalmazottat sem vont be projektmunkába? Van(nak) olyan részleg(ek), ahonnan mindenki csatlakozott? Vannak a feladatkiosztásban olyan aránytalanságok, amelyek kimutathatók és így a későbbiek során korrigálhatók? Készítsünk egy kimutatást arról, hogy részlegenként hány fő vett részt projektmunkában és mi a létszám! (Persze tudjuk, hogy nem minden munkakörből vonhatók be alkalmazottak.) Milyen projektjeink szoktak lenni? Van olyan részleg, ahol érdemes bővíteni a létszámot, esetleg átcsoportosítani oda erőforrást? Ezekre a kérdésekre keressük a választ.

Tervezés

Az Oracle HR sémában három tábla kapcsolódik a feladathoz: JOB_HISTORY, EMPLOYEES, DEPARTMENTS. A kapcsolatok fokszámai láthatók az alábbi ábrán. Egy részlegben több alkalmazott is lehet. Egy alkalmazott részt vehetett korábban több projektmunkában is.

Oracle HR séma

A DEPARTMENTS táblában található a részleg azonosítója ( DEPARTMENT_ID, kulcs) és neve ( DEPARTMENT_NAME). A többi adat most nem kell. 11 olyan részleg van, amihez tartozik alkalmazott.

A JOB_HISTORY tábla tárolja, hogy a már befejeződött projektekben ki ( EMPLOYEE_ID, külső kulcs) és melyik részlegből ( DEPARTMENT_ID, külső kulcs) vett részt. A dátumokat ( START_DATE, END_DATE) és a munkakör külső kulcsát ( JOB_ID) most nem használjuk. Minden projekt lezárt. 10 lezárt projekt van.

Az EMPLOYEES táblából szükséges az alkalmazott azonosítója ( EMPLOYEE_ID, kulcs), valamint részlegének azonosítója ( DEPARTMENT_ID, külső kulcs). A többi adatra most nincs szükség, de egy részletesebb – például név szerinti – kimutatáshoz már igen. 106 olyan alkalmazott van, akihez tartozik részleg (1-nek nincs).

Hozzunk létre négy oszlopból álló eredménytáblát: DEPARTMENT_ID, DEPARTMENT_NAME, COUNT_PROJECT_EMPLOYEES, COUNT_EMPLOYEES. Ennek áttekintésével választ kaphatunk a fenti kérdésekre.

1. megoldás

Induljunk ki abból, hogy a JOB_HISTORY táblában lévő DEPARTMENT_ID-hez hozzárendeljük a DEPARTMENTS táblából a DEPARTMENT_NAME-t. Ezekre csoportosítva könnyen aggregálható az adott részlegből projektmunkát végző alkalmazottak száma: COUNT_PROJECT_EMPLOYEES. Végül egy belső lekérdezés (összekapcsolva a JOB_HISTORY és az EMPLOYEES táblákat) megadja az adott részleg alkalmazotti létszámát. Az SQL lekérdezés:

SQL-megold1a

A részeredmény:

SQL-eredmeny1a

Ezután állítsuk elő a hiányzó adatokat! Tudjuk, hogy azokban a részlegekben, amelyek DEPARTMENT_ID-je nem szerepel a JOB_HISTORY táblában, de szerepel az EMPLOYEES táblában, azok léteznek, de nem „adtak” projektmunkára alkalmazottat (azaz COUNT_PROJECT_EMPLOYEES=0). Nevük és alkalmazottaik száma ugyanúgy megadható, ahogyan az előbb. Az SQL lekérdezés:

SQL-megold1b

A részeredmény:

SQL-eredmeny1b

A két részeredményt egyesíteni kell és egyben hasznos DEPARTMENT_NAME szerint növekvő sorrendbe rendezni az alábbi lekérdező paranccsal:

SQL-megold1c

Az eredmény:

SQL-eredmeny1c

2. megoldás

Kiindulhatunk abból is, hogy a DEPARTMENTS egy szótártábla, így közvetlenül hozzáférhető a DEPARTMENT_ID és a DEPARTMENT_NAME, de össze kell kapcsolni az EMPLOYEES táblával, hogy csak olyan részlegeket adjon vissza a lekérdezés, ahol van(nak) alkalmazott(ak). Az eredményhez szükséges további két oszlop könnyen aggregálható az adott részlegre vonatkozóan: a JOB_HISTORY táblában előforduló EMPLOYEE_ID-k száma adja a COUNT_PROJECT_EMPLOYEES-t (probléma nélkül tud 0 lenni) és az EMPLOYEES táblában előforduló EMPLOYEE_ID-k száma adja a COUNT_EMPLOYEES-t. A rendezés most is szükséges. Lényegesen tömörebb lekérdező parancsot kapunk:

SQL-megold2

Az eredményül kapott táblázat megegyezik az 1. megoldás eredményével.

A két megoldás teljesen különböző gondolatmenettel született. Mindkettőben vannak olyan elemek, amelyek – konkrét feladatból általánosítva – univerzálisan használhatók. Természetesen összehasonlítjuk a két megoldás végrehajtási tervét és részletesen elemezzük is.

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 adatbázis-kezelő tanfolyam 9-12. óra: Oracle HR séma elemzése, 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalomhoz kapcsolódik.

Az SQL forráskód formázásához a Free Online SQL Formatter-t használtam.

Organogram készítése

Organogram logó

HR-organogram-logoAz Oracle HR sémából építünk organogramot, amivel megjeleníthető a szervezeti hierarchia. Személyenként készítünk csomópontokat. (Másképpen is lehetne: például részlegenként.) A megvalósítás során kétszer konvertálunk A-ból B-be. Először az adatbázisból/adatforrásból SQL lekérdezéssel jutunk hozzá a szükséges adatokhoz, amelyeket generikus listába képezzük le. Ezután a listát feldolgozva generálunk HTML fájlt, amely tartalmaz egy Organization Chart diagramot.

Hasonló feladat: Ki kinek a vezetője?, rekurzív lekérdezéssel. Érdemes összehasonlítani a kétféle szemléletmódot.

Tervezés

Most pedig azt használjuk fel, hogy az Oracle HR sémában az EMPLOYEES táblában reflexió van, amelyet az EMPLOYEE_ID és a MANAGER_ID mezők biztosítanak.

Az Organization Chartnál három adatsor adható meg. Ezek most testre szabva (mindegyik szöveges): 'Employee lastname', 'Job ID', valamint jelmagyarázatként további három mező összefűzve: 'Employee name, Department name, Job title'. Az organogramon megjelenő adatok például: "Raphaely", "PU_MAN", valamint a csomópontra fókuszálva megjelenő tooltip: "Employee: Den Raphaely, Department: Purchasing, Job: Purchasing Manager". A DEPARTMENTS táblából – az EMPLOYEES-zel a DEPARTMENT_ID-vel összekötve – megkapjuk a DEPARTMENT_NAME-t. A JOBS táblából pedig – az EMPLOYEES-zel a JOB_ID-vel összekötve – megkapjuk a JOB_TITLE-t.

A lekérdező parancs

SQL-organogram

Az EMPLOYEE_ID elsődleges kulcs, vagyis kötelező. A MANAGER_ID nem kötelező, a hierarchia tetején álló vezetőnél ez a mező null értékű. Mivel a MANAGER_ID nem kötelező, így külön lekérdező parancsban kell előállítani a 15 középvezetőt együtt a 2 felső vezetővel, valamint az egyetlen felső vezetőt, akinek a MANAGER_ID-ja null. Ezt a két részeredményt össze kell fűzni ( UNION).

Az eredménytábla

SQL-eredménytábla

Az adatfeldolgozás lépései

Java programozási nyelven kötelező a kivételkezelés a JDBC kapcsolatfelvétel, SQL parancs futtatása, valamint a fájlkezelés során. A JDBCConnection interfészben definiált szöveges konstansok: DRIVER, URL, USER, PASSWORD (az adatbázis-szerverrel való kommunikációhoz), SQL (a lefuttatandó lekérdező parancs). Az OrganizationChart interfészbe került a HTML_FILE_PATH (a generálandó HTML fájl Path útvonala) és a HTML (konstans váz az organogram testre szabott HTML+JavaScript forráskódja). Az SQL parancs ResultSet eredménytáblájának feldolgozása során áll elő az orgChartDataList generikus lista. A HTML konstans szövegben lévő #OrgChartData# elemet ki kell cserélni a generikus listából Stream API-val dinamikusan összefűzött adatokra. A fenti példa ide kapcsolódó része: "[{'v':'Raphaely', 'f':'Raphaely<div style="color:red; font-style:bold">PU_MAN</div>'}, 'King', 'Employee: Den Raphaely, Department: Purchasing, Job: Purchasing Manager']". Ezt követően a java.nio csomag Files osztályának write() metódusával fájlba menthető az előállított fájltartalom. A konkrét Java forráskódot most nem részletezem.

Az elkészült organogram

HR-organogram

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 adatbázis-kezelő tanfolyam 9-12. óra: Oracle HR séma elemzése, 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalomhoz kapcsolódik.

Az SQL forráskód formázásához a Free Online SQL Formatter-t használtam.