A következő címkéjű bejegyzések mutatása: JavaSE8. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: JavaSE8. Összes bejegyzés megjelenítése

2015. április 2., csütörtök

Java SE 8 - Stream API további példák

A Stream API lehetőségeit érdemes minél jobban kihasználni, ha már Java SE 8-on fejlesztünk, ezért a múltkori cikket folytatva nézzünk meg még néhány használati példát.

Az alábbi kód megjeleníti a legtöbb karaktert tartalmazó String-et a listából. Látható, hogy milyen elegánsan használható a Comparator funkcionális interfész comparing() statikus metódusa mivel nem kell mindkét összehasonlítandó objektumot definiálni, csak azt kell meghatározni egy lambda kifejezéssel, hogy miszerint végezzük el az összehasonlítást. A max() hívás visszatérési értéke egy Optional<T> típusú objektum, amitől lekérdezhetjük az eredményt egy get() hívással vagy akár azt is hogy volt-e egyáltalán valamilyen eredmény: isPresent().
List<string> fruitList = 
 Arrays.asList("Alma","BaNÁN","KÖRTE","szILVA","BaracKok","kiwi");

String max=fruitList.stream()
 .max(Comparator.comparing(s -> s.length())).get()

System.out.println(max); //BaracKok
A következő példa a listában található sztringeket összefűzi az első 3 karakterük felhasználásával. A sztringeket 3 karakteres formára leképező map() már ismerős lehet az előző cikkből, azonban van itt egy újdonság is a reduce(). A reduce segítségével a Collection elemeit egy adott értékre redukálhatjuk le, hasonlóan ahogy a korábban bemutatott max() is működik. A példánál maradva, itt a reduce feladata az lesz, hogy a 3 karakteres sztringeket összefűzze egy String-be.
String concat=fruitList.stream().map(s -> s.substring(0, 3))
 .reduce("",(acc,curr) -> acc+curr);

System.out.println(concat); //AlmBaNKÖRszIBarkiw
A végére hagytam a kedvencemet a flatMap metódust, amivel egy listákat tartalmazó lista elemeit a lehető legelegánsabb módon dolgozhatunk fel. A Java 8 előtti időkben ehhez még 2 for ciklust kellett volna használnunk...
List<List<String>> list = new ArrayList<>();
List<String> sList1 = Arrays.asList("aaa", "bbb", "ccc");
List<String> sList2 = Arrays.asList("ddd", "eee", "fff");
List<String> sList3 = Arrays.asList("ggg", "hhh", "iii");  
list.add(sList1);list.add(sList2);list.add(sList3);

// external iteration
for(List<String> ls: list)
 for(String s: ls)
  System.out.println(s);  

// internal iteration
list.stream().flatMap(l->l.stream())
       .forEach(e->System.out.println(e)); 
Hát ennyi lett volna a Stream API, a folytatás hamarosan következik.

2015. március 3., kedd

Java SE 8 - Stream API bevezető

Legutóbb a Java 8 funkcionális interfészeivel ismerkedtünk meg, most pedig következzen a Stream API. A Java 8 könyvtárakhoz kapcsolódó legnagyobb változások a Collections API-t érintették, többek között megalkották a Stream API-t, amivel a Collection-ben lévő objektumokat - kihasználva a Lambda kifejezések lehetőségeit - most már stream-ként is feldolgozhatunk.

A Stream API előtti időkben ha egy listát akartunk bejárni, akkor külső (external) iterációt alkalmaztunk, ami feleslegesen terjengőssé tette a kódot valamint a párhuzamosított végrehajtásra való átállás esetén az egészet újra kellett írnunk. A Stream API egy olyan fluent megoldást ad a kezünkbe, ahol nem kell külön for ciklusokat definiálnunk, az iteráció (internal) részleteit lekezeli az API.
List<String> fruitList = 
 Arrays.asList("Alma","BaNÁN","KÖRTE","szILVA","BaracKok","kiwi");

// external iteration
long xcount = 0;
for (String str : fruitList) {
 if (str.length() == 4)
  xcount++;
}
System.out.println(xcount); //2

// internal iteration with Stream API
xcount=fruitList.stream().filter(str -> str.length() == 4).count()
System.out.println(xcount); //2
Az fenti kódrészlet megszámolja a 4 karakteres String-eket a Java 8 előtti és a Stream API által bevezetett iteráció használatával. Az internal iterációnál maradva, a stream() hívással elkérjük a listában található objektumok stream-jét, majd a Lambda kifejezést használó intermediate filter() művelettel kiválogatjuk a 4 karaktereseket, végül a count() terminate hívással lekérdezzük ezen elemek darabszámát. Érdemes tudni, hogy az intermediate műveletek csupán konfigurációs (lazy) feladatot látnak el, a tényleges bejárás a terminate (eager) művelet során történik meg.
Set<String> set = fruitList.stream()
 .filter(str -> str.length() != 4)
 .map(str -> str.toUpperCase()).collect(Collectors.toSet());

System.out.println(set); // [BARACKOK, KÖRTE, BANÁN, SZILVA]
A következő példa a nem 4 karakteres Stringeket nagybetűssé alakítja, majd az eredményt egy Set-be teszi. A map() konfigurációs művelet az értékeket egy másik értékre konvertálja át, az esetünkben minden egyes String-et - ami a filter()-ben definiált feltételnek megfelel - nagybetűssé alakít. A collect() hívással megkezdődik a Stream feldolgozása és a megfelelő objektumok bekerülnek az új Set-be.

A folytatásban további példákat fogok mutatni a Stream API használatára. De most legyen egy kis házi feladat: a filter() és a map() milyen funkcionális interfészt használ paraméterként?

2014. december 12., péntek

Java SE 8 - Funkcionális interfészek

Miután megismerkedtünk a Lambda kifejezésekkel, folytassuk az utunkat a funkcionális interfészekkel amiknek a jellemzője, hogy csak egyetlen egy absztrakt metódussal rendelkeznek és a Lambda kifejezések típusát fogják majd meghatározni. A metódus neve bármi lehet, a lényeg hogy a szignatúrája kompatibilis legyen a Lambda kifejezéssel. Funkcionális interfészekkel már korábban is találkozhattunk a Java nyelvben (Runnable, Callable, Comparator), azonban a JDK8-at is kiegészítették jó pár generikus funkcionális interfésszel:
// Predicate<T>
Predicate<Integer> p = x -> x > 5;
System.out.println(p.test(10)); //true

// Consumer<T>
Consumer<Integer> c = i -> System.out.println(i);
c.accept(111); //111

// Function<T,R>
Function<Integer, String> f = i -> i > 10 ? "OK" : "NOT OK";
System.out.println(f.apply(100)); //OK

// Supplier<T>
Supplier<Integer> s = () -> 66;
System.out.println(s.get()); //66

// UnaryOperator<T>
// return használatakor a {}-t ki kell tenni 
UnaryOperator<Boolean> u = (bool) -> {return !bool;};
System.out.println(u.apply(false)); //true

// BinaryOperator<T>
BinaryOperator<Double> b = (Double d1, Double d2) -> d1 + d2;
System.out.println(b.apply(11d, 22d)); //33.0
A fenti kódrészleten látható Predicate funkcionális interfész egyetlen egy absztrakt metódust tartalmaz (test()), ami paraméterként Integer típust vár és a visszatérési értéke boolean. Érdemes megemlíteni, hogy a paraméter típusát nem kötelező definiálni, mivel a Type Inference feature segítségével a java compiler ezt képes kikövetkeztetni.

A beépített funkcionális interfészek mellett akár sajátot is létrehozhatunk és opcionálisan elláthatjuk a @FunctionalInterface annotációval, így a fordító azonnal szól ha esetleg több absztrakt metódust is defniáltunk volna az interface-ben.

A folytatásban a Stream API következik.

2014. december 1., hétfő

Java SE 8 - Lambda kifejezések

A Java SE 7 újdonságai után haladjunk a korral és nézzük meg a Java SE 8 leginkább várt feature-ét, a Lambda kifejezéseket. A Lambda kifejezések és a hozzá kapcsolódó újítások (Stream API, funkcionális interfészek, alapértelmezett metódusok, metódus referenciák) azért is jelentősek, mivel a Java SE 5 verzióban megjelent generikusok és annotációk óta nem volt ilyen nagy változás, ami ennyire átalakította volna a Java nyelv használatát. A lambda kifejezésekkel tisztább, sokkal kifejezőbb és így karbantarthatóbb kódot írhatunk valamint már nyelvi szinten is kihasználhatjuk a több processzoros gépek párhuzamos feldolgozási képességeit. De mik is azok a lambda kifejezések?

A lambda kifejezések olyan névtelen metódusok, amiket ott írunk meg ahol ténylegesen használunk. Egy remek példa erre az anonim osztályokkal megvalósított Swing-es eseménykezelők átírása lambda kifejezések használatával:
Button btn = new Button();

//anonymous inner class
btn.addActionListener(new ActionListener() {
 public void actionPerformed(ActionEvent event) {
  System.out.println("print");
 }
});

//lambda expression
btn.addActionListener(event -> System.out.println("print"));
A lambda kifejezéssel magát a viselkedést, tehát az implementációt adjuk át az addActionListener() metódusnak, nem pedig egy olyan objektumot ami megvalósítja a kívánt interfészt. A fenti példa alapján beazonosítható a Lambda kifejezések szintaktikája, azaz definiáljuk a paramétereket - akár a típus megnevezése nélkül - (event), a nyíl tokent (->) majd az implementációt (System.out.println("print")).

A továbbiakban nézzünk meg néhány mintát a Lambda kifejezésekre. Az első példában egy Comparator-nak adjuk meg az implementációját, majd a 10 és 20 értékre vonatkozólag megjelenítjük az eredményt. A második példa pedig egy argumentum nélküli, több soros implementációval rendelkező Lambda kifejezést mutat.
// sample1
Comparator<Integer> myComparator = (a, b) -> a.compareTo(b) * -1;
System.out.println(myComparator.compare(10, 20));

// sample2
int i = 10;
// i=777; fordítási hiba, mivel az i változó "effectively final"
Runnable r2 = () -> {
 int c = i + 8;
 System.out.println("Adder: " + c);
};
r2.run();
Érdemes megemlíteni, hogy az i változó értékét nem módosíthatjuk annak ellenére, hogy a final módosító nincs kiírva, mivel azt egy lambda kifejezésben is használjuk. (effectively final)

A cikk hamarosan folytatódik, legközelebb a Lambda kifejezésekhez szorosan kapcsolódó funkcionális interfészekről fogok blogolni. Egyébként használja már valaki a Java 8-ast éles fejlesztéshez? :)