Java 8 strimovi

Share

Kao junior full stack programer u betting timu MozzartBetovog IT sektora jedan od izazova koji me svakodnevno motivišu je na primer kako kreirati kolekcije transakcije klađenja da bi reprezentovali igračev obračun, i onda procesirati celu kolekciju kako bi se pronašao podatak o tome koliko je igrač potrošio novca. Za većinu ovakvih problema, najčešće se nameće korišćenje Jave 8. Da bih ubrzao procesiranje oslonio sam se na višejezgarnu arhitekturu, ali pisanje paralelnog koda je teško i podložno greškama, a standardno procesiranje kolekcija je daleko od savršenog u Javi.

Java obezbeđuje novi dodatni paket u Javi 8 koji se naziva java.util.stream. Ovaj paket se sastoji od klasa, interfejsa i nabrojivih tipova kako bi dozvolio funkcionalni stil operacija nad elementima. Java 8 uvodi koncept strima koji dopušta programeru da procesira podatke opisno i da se osloni na višejezgarnu arhitekturu bez potrebe da piše specijalan kod o tome.

Strim predstavlja sekvencu objekata dobijenih od izvora, nad kojima se mogu izvoditi agregatne operacije.

Karakteristike strima su sledeće:

Sekvenca elemenata − Strim obezbeđuje set elemenata određenog tipa u sekvencijalnom maniru. Strim dobija element na zahtev. Nikada  ne store-uje element.

Izvor − Strim uzima kolekcije, nizove, ili I/O resurse kao izvor.

Agregatne operacije − Strim podržava agregatne operacije kao što su forEach, filter, map, sorted i druge.

Nadovezivanje − Većina operacija nad strimom vraća strim pa to znači da se njihov rezultat može nadovezivati. Funkcija ovih operacija je da uzme ulazne podatke, procesira ih, i vrati izlaz meti. collect() metoda je terminalna operacija koja je obično prisutna na kraju nadoveznih operacija da označi kraj strima.

Automatske iteracije − Strim operacije sprovode iteracije interno nad izvorom elemenata, za razliku od kolekcija gde je potrebno navoditi eksplicitnu iteraciju.

Možemo generisati strim sa dve metode:

stream() – Vraća sekvencijalni strim smatrajući kolekciju kao svoj izvor.

parallelStream() − Vraća paralelni strim smatrajući kolekciju kao svoj izvor.

Kada je strim generisan, možemo koristiti neku od dosta obezbeđenih metoda:

– forEach da bi iterirali kroz svaki element strima

Random random = new Random();

random.ints().limit(15).forEach(System.out::println);

Ovaj segment koda prikazuje kako da ispišemo 15 slučajnih brojeva koristeći forEach.

– map se koristi da mapiramo svaki element svom korespodentnom rezultatu.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList())

Ovaj segment koda prikazuje skupljanje kvadrata brojeva koristeći map.

– filter da eliminišemo elemente na osnovu nekog kriterijuma.

List<String>strings = Arrays.asList("MozzartBet", "", "Java 8", "Stream", "filter","");

int count = strings.stream().filter(string -> string.isEmpty()).count();

Ovaj segment koda ispisuje broj praznih stringova koristeći filter.

-sorted da sortiramo strim.

Random random = new Random();

random.ints().limit(15).sorted().forEach(System.out::println);

Ovaj segment koda prikazuje kako da se ispiše 15 slučajnih brojeva u sortiranom rasporedu.

List<String>strings = Arrays.asList("MozzartBet", "", "Java 8", "parallelStream", "filter","");

int count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Kao što možemo videti, kod je dosta kraći i lakše je prebacivati se sa sekvencijalnih i paralelnih strimova nego sa istim kodom u Javi 7. Takođe strimovi su lakši i bolji za korišćenje kada se upare sa Lambda funkcijama.

Rezultat sledeća dva parčeta koda je logički isti

List<Integer> list = Arrays.asList(1, 2, 3);

// Stara škola

for (Integer i : list)

System.out.println(i);

// „Moderno“

list.forEach(System.out::println);

“Moderan” pristup treba koristiti ekstremno pažljivo, tj. samo  ako imamo čist benefit od internih, funkcionalnih iteracija.

Ovde je kratka lista problema  “modernog” pristupa u odnosu na klasičan pristup:

Performance – U dosta slučajeva, performanse nisu kritičan faktor, i ne treba raditi nikakvu optimizaciju. Ali ako pričamo o usporenju CPU-a od oko 20% samo na osnovu izbora stila ciklusa, onda smo uradili nešto fundamentalno pogrešno.

Ovde je reper rezultata običnog ciklusa, pronačazeći maksimalnu vrednost u listi upakovanih intova:

ArrayList, for-loop : 6.55 ms

ArrayList, seq. stream: 8.33 ms

Ovde je reper rezultata običnog ciklusa, pronačazeći maksimalnu vrednost u listi primitivnih intova:

int-array, for-loop : 0.36 ms

int-array, seq. stream: 5.35 ms

Čitljivost

Uvek ćemo diskutovati o stilu našeg koda kao o stvari sa velikom važnošću. Na primer o broju razmaka ili o vitičastim zagradama.

Razlog za to je teško razumevanje softvera. Posebno koda koji je pisao neko drugi, pre mnogo vremena.

Ovde je primer kako je kod pisan“starom školom” mnogo čitljiviji od “modernog” koda:

List<Integer> list = Arrays.asList(1, 2, 3);

// Stara škola
    for (Integer i : list)
      for (int j = 0; j < i; j++)
    System.out.println(i * j);

// "Moderno"
list.forEach(i -> {
   IntStream.range(0, i).forEach(j -> {
       System.out.println(i * j);
   });
});

Ne postoji crno ili belo rešenje za ovaj problem. Ali ako je ostatak koda imperativan , onda ugnježdavanje deklaracija, forEach() poziva, i lambdi je neuobičajeno.

Održivost – Pogledajmo sledeći segment koda:

List<Integer> list = Arrays.asList(1, 2, 3);

// Stara škola
    for (Integer i : list)
      for (int j = 0; j < i; j++)
    System.out.println(i / j);

// "Moderno"
list.forEach(i -> {
   IntStream.range(0, i).forEach(j -> {
     System.out.println(i / j);
   });
});

Stara škola

Exception in thread "main" java.lang.ArithmeticException: / by zero

at Test.main(Test.java:13)

Moderno

Exception in thread "main" java.lang.ArithmeticException: / by zero

at Test.lambda$1(Test.java:18)

at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)

at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:557)

at Test.lambda$0(Test.java:17)

at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)

at Test.main(Test.java:16)

Ovde su glavni razlozi zašto smo imali loše performanse u tački pod 1. Interna iteracija zahteva puno više rada JVM i za biblioteke.

– Napomena – Svi paralelni strimovi koji se pozivaju za isti servis koriste zajednički fork-join thread pool ako za neki od njih nije kreiran poseban pool. 

Zaključak

Kada se prebacite na Javu 8 i razmišljate o korišćenju više funkcionalnog stila u vašem kodu, imajte u vidu da funkcionalno programiranje nije uvek bolje. U stvari, to je samo drugačiji pristup i dopušta nam da priđemo problemu drugačije. Java developerima će biti potrebna praksa, da odluče kada je najbolje koristiti funkcionalno programiranje, a kada objektno orijentisano/imperativno. Sa dovoljnim nivoom vežbe, kombinovanje oba principa će nam pomoći da unapredimo naš softver.

Share

Prijavi se da prvi dobijaš nove blogove i vesti.

Ostavite odgovor

Mladen Branković

Junior full stack developer @ MozzartBet
mm

Rođen u Valjevu, diplomirao je na Elektrotehničkom fakultetu u Beogradu, Odsek za računarstvo i informacione tehnologije. Trenutno radi za MozzartBet kao junior full stack developer. Ima iskustvo u razvoju Java, Javascript i AngularJS. Radio je na softverskom rešenju za upozoravanje korisnika o eskalacijama u sistemu klađenja i multimedijalnim pogledima na sportske rezultate koji se prikazuju u prostorijama za klađenje MozzartBeta.

Prijavi se da prvi dobijaš nove blogove i vesti.

Kategorije