Korá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.
1 2 3 4 5 6 |
List<Integer> list=new Random().ints(100-1, 10, 99+1). boxed().collect(Collectors.toList()); list.add(80); System.out.println("100 db kétjegyű véletlenszám\n"+ list.stream().sorted().map(Number::toString). collect(Collectors.joining(", "))); |
2. Elemi programozási tételek
2.1. Sorozatszámítás
Kiírjuk, hogy mennyi a listában lévő számok összege:
1 2 3 4 5 6 7 8 9 |
System.out.print("Mennyi a számok összege? "); System.out.print("V1: "+ list.stream().reduce(0, Integer::sum)+", "); System.out.print("V2: "+ list.stream().reduce(0, (a, b) -> a+b)+", "); System.out.print("V3: "+ list.stream().mapToInt(Integer::intValue).sum()+", "); System.out.println("V4: "+ list.stream().collect(Collectors.summingInt(Integer::intValue))); |
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)?
1 2 3 4 5 6 7 8 9 10 11 |
System.out.print("Van-e a számok között 35? "); System.out.print("V1: "+ (list.stream().anyMatch(n -> n==35)?"Van.":"Nincs.")+", "); System.out.print("V2: "+ (list.stream().filter(n -> n==35).count()>0)+", "); System.out.print("V3: "+ (list.stream().filter(n -> list.contains(35)).count()>0)+", "); System.out.println("V4: "+ list.stream().filter(n -> n==35).findFirst().isPresent()); System.out.println("Van-e a számok között páros? "+ (list.stream().anyMatch(n -> n%2==0)?"Van.":"Nincs.")); |
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:
1 2 3 |
System.out.println("Melyik a(z első) 80 sorszáma? "+ IntStream.range(0, list.size()). filter(i -> list.get(i)==80).mapToObj(i -> i).findFirst().get()); |
2.4. Keresés
Keressük a 35-öt az eldöntés és a kiválasztás összeépítésével:
1 2 3 4 5 |
System.out.println("Van-e 35? Ha igen, melyik a(z első) sorszáma? "+ (list.stream().anyMatch(n -> n==35) ? "Van, a(z első) sorszáma: " + IntStream.range(0, list.size()). filter(i -> list.get(i)==35).mapToObj(i -> i).findFirst().get() : "Nincs.")); |
2.5. Megszámolás
Kiírjuk, hogy hány db öttel osztható szám (adott tulajdonságú elem) található a listában:
1 2 |
System.out.println("Az öttel osztható számok száma: "+ list.stream().filter(n -> n%5==0).count()); |
2.6. Szélsőérték-kiválasztás
Kiírjuk a listában lévő legkisebb számot (értéket, nem indexet):
1 2 |
System.out.println("A legkisebb szám: "+ list.stream().mapToInt(n -> n).min().getAsInt()); |
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):
1 2 3 4 5 |
List<Integer> copyList= list.stream()./*map(n -> 2*n).*/collect(Collectors.toList()); System.out.println("Másolat a listáról: "+ copyList.stream().map(Number::toString). collect(Collectors.joining(", "))); |
3.2. Kiválogatás
A listában lévő számok közül kiválogatjuk az öttel osztható számokat:
1 2 3 4 5 6 7 8 |
List<Integer> selectedList=new ArrayList<>(); list.stream().filter(n -> n%5==0).forEach(selectedList::add); System.out.println("Az öttel osztható számok (V1): "+ selectedList.stream().map(Number::toString). collect(Collectors.joining(", "))); System.out.println("Az öttel osztható számok (V2): "+ list.stream().filter(n -> n%5==0). map(Number::toString).collect(Collectors.joining(", "))); |
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:
1 2 3 4 5 6 7 8 9 10 |
List<Integer> evenList=new ArrayList<>(); //páros List<Integer> oddList=new ArrayList<>(); //páratlan list.stream().forEach(n -> ((n%2==0) ? evenList : oddList).add(n)); System.out.println("Szétválogatás után:\n"+ "párosak: "+ evenList.stream().map(Number::toString). collect(Collectors.joining(", "))+"\n"+ "páratlanok: "+ oddList.stream().map(Number::toString). collect(Collectors.joining(", "))); |
3.4. Unió
A korábban szétválogatott páros és páratlan számokat tartalmazó halmazok unióját állítjuk elő:
1 2 3 4 5 6 7 |
List<Integer> unionList=new ArrayList<>(); Stream.concat(evenList.stream().distinct(), oddList.stream().distinct()). distinct().forEach(unionList::add); System.out.println("Párosak és páratlanok uniója:\n"+ unionList.stream().map(Number::toString). collect(Collectors.joining(", "))); |
3.5. Metszet
A korábban szétválogatott páros és páratlan számokat tartalmazó halmazok metszetét állítjuk elő:
1 2 3 4 5 6 |
List<Integer> intersectionList=new ArrayList<>(); evenList.stream().distinct().filter(oddList::contains). forEach(intersectionList::add); System.out.println("Párosak és páratlanok metszete:\n"+ intersectionList.stream().map(Number::toString). collect(Collectors.joining(", "))); |
3.6. Összefésülés
A korábban szétválogatott páros és páratlan számokat összefésüljük:
1 2 3 4 5 6 |
List<Integer> mergedList=new ArrayList<>(); Stream.concat(evenList.stream(), oddList.stream()).forEach(mergedList::add); System.out.println("Párosak és páratlanok összefésülve:\n"+ mergedList.stream().map(Number::toString). collect(Collectors.joining(", "))); |
4. A program eredménye a konzolon
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
100 db kétjegyű véletlenszám 10, 10, 10, 11, 13, 14, 16, 16, 18, 19, 19, 20, 21, 22, 23, 23, 24, 25, 25, 25, 26, 29, 29, 30, 30, 31, 32, 33, 34, 35, 36, 38, 38, 39, 39, 40, 42, 45, 45, 46, 47, 48, 49, 49, 50, 50, 51, 51, 53, 53, 54, 54, 54, 55, 56, 59, 61, 62, 62, 63, 63, 64, 67, 67, 68, 68, 69, 70, 72, 72, 73, 77, 77, 77, 77, 77, 79, 79, 79, 80, 81, 83, 84, 85, 85, 87, 87, 89, 89, 89, 89, 93, 95, 95, 95, 96, 97, 97, 98, 99 Mennyi a számok összege? V1: 5430, V2: 5430, V3: 5430, V4: 5430 Van-e a számok között 35? V1: Van., V2: true, V3: true, V4: true Van-e a számok között páros? Van. Melyik a(z első) 80 sorszáma? 99 Van-e 35? Ha igen, melyik a(z első) sorszáma? Van, a(z első) sorszáma: 22 Az öttel osztható számok száma: 23 A legkisebb szám: 10 Másolat a listáról: 20, 69, 48, 96, 19, 98, 11, 77, 49, 81, 79, 95, 70, 77, 67, 36, 23, 72, 38, 38, 54, 89, 35, 51, 83, 64, 26, 25, 51, 30, 89, 50, 22, 62, 95, 39, 42, 14, 10, 87, 54, 84, 24, 89, 16, 72, 97, 25, 85, 61, 59, 19, 13, 68, 55, 31, 79, 32, 47, 29, 18, 56, 10, 53, 21, 25, 77, 85, 39, 45, 67, 63, 53, 40, 50, 29, 77, 16, 46, 99, 63, 45, 77, 97, 87, 89, 23, 30, 93, 95, 49, 68, 10, 34, 54, 33, 62, 79, 73, 80 Az öttel osztható számok (V1): 20, 95, 70, 35, 25, 30, 50, 95, 10, 25, 85, 55, 10, 25, 85, 45, 40, 50, 45, 30, 95, 10, 80 Az öttel osztható számok (V2): 20, 95, 70, 35, 25, 30, 50, 95, 10, 25, 85, 55, 10, 25, 85, 45, 40, 50, 45, 30, 95, 10, 80 Szétválogatás után: párosak: 20, 48, 96, 98, 70, 36, 72, 38, 38, 54, 64, 26, 30, 50, 22, 62, 42, 14, 10, 54, 84, 24, 16, 72, 68, 32, 18, 56, 10, 40, 50, 16, 46, 30, 68, 10, 34, 54, 62, 80 páratlanok: 69, 19, 11, 77, 49, 81, 79, 95, 77, 67, 23, 89, 35, 51, 83, 25, 51, 89, 95, 39, 87, 89, 97, 25, 85, 61, 59, 19, 13, 55, 31, 79, 47, 29, 53, 21, 25, 77, 85, 39, 45, 67, 63, 53, 29, 77, 99, 63, 45, 77, 97, 87, 89, 23, 93, 95, 49, 33, 79, 73 Párosak és páratlanok uniója: 20, 48, 96, 98, 70, 36, 72, 38, 54, 64, 26, 30, 50, 22, 62, 42, 14, 10, 84, 24, 16, 68, 32, 18, 56, 40, 46, 34, 80, 69, 19, 11, 77, 49, 81, 79, 95, 67, 23, 89, 35, 51, 83, 25, 39, 87, 97, 85, 61, 59, 13, 55, 31, 47, 29, 53, 21, 45, 63, 99, 93, 33, 73 Párosak és páratlanok metszete: Párosak és páratlanok összefésülve: 20, 48, 96, 98, 70, 36, 72, 38, 38, 54, 64, 26, 30, 50, 22, 62, 42, 14, 10, 54, 84, 24, 16, 72, 68, 32, 18, 56, 10, 40, 50, 16, 46, 30, 68, 10, 34, 54, 62, 80, 69, 19, 11, 77, 49, 81, 79, 95, 77, 67, 23, 89, 35, 51, 83, 25, 51, 89, 95, 39, 87, 89, 97, 25, 85, 61, 59, 19, 13, 55, 31, 79, 47, 29, 53, 21, 25, 77, 85, 39, 45, 67, 63, 53, 29, 77, 99, 63, 45, 77, 97, 87, 89, 23, 93, 95, 49, 33, 79, 73 |
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!
Nekem nagyon szokatlan volt elsőre ez a funkcionális szemlélet. Talán 2.-ra is. 🙂 Értem, hogy ez is OOP és kezdek ráállni a szintaxisra. A tesztelését nehézkesnek vélem, olyasmi mint a rekurzió: vagy működik (és akkor jól) vagy nem.
Zoli: jól látod. Tudod: gyakorolni, gyakorolni, gyakorolni.
Az összetett PT-eket gyakoroltam úgy, hogy az eredmények IntStream-be kerülnek. Megmutatom majd a hétvégi órán. Olyan kérdéseim is vannak, amiket szerintem a többieknek is „hallani kell”.
OK Zoli, várjuk a kérdéseidet vasárnap.
Mi a helyzet a gyakran előkerülő hatékonysággal, ha Stream API-t használunk?
Van erről egy részletes prezentációm Dani. Eléred az ILIAS-on, keresd a kollekcióknál.
A téma népszerűsége okán ajánlom lambda kifejezés címkénket a szakmai blogból:
https://it-tanfolyam.hu/cimke/lambda-kifejezes/
Rendszeresen blogolunk erről.