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? :)

2014. november 18., kedd

A Java Standard Edition 7 újdonságai

A munkámból adódóan sok vállalatnál megfordulok és azt látom, hogy a legtöbb helyen még mindig csak a Java 6-os verzióját használják annak ellenére, hogy 2014 március óta már a Java SE 8 is elérhető. Habár a hivatalos roadmap szerint a Java SE 7 publikus frissítései csak 2015 áprilisig várhatóak, érdemes a legfontosabb újdonságokat megismerni, mert azok természetesen a Java SE 8 verzióban is használhatók. Ezekből feature-ökből gyűjtöttem most össze néhányat.

1. Numerikus literáloknál aláhúzásjel használata

Az aláhúzásjelek használatának a célja, hogy megkönnyítse a számok olvashatóságát.
class Numbers {
 public static void main(String args[]) {
  int i = 1_000_000;
  long m = 9_999_999_999_999L;
  double pi = 3.14_15;
 }
}

2. String használata switch szerkezetben

A String equals() metódusával lesz kiértékelve a kifejezés. Érdemes tudni, hogy a Java compiler hatékonyabb bytekódot generál ebben az esetben, mintha az if-else szerkezetet használnánk!
class StringSwitch {
 public static void main(String args[]) {
  String fruit = "apple";
  switch (fruit) {
   case "orange":
    System.out.println("orange");
    break;
   case "apple":
    System.out.println("apple");
    break;
   case "banana":
    System.out.println("banana");
    break;
  }
 }
}

3. Erőforrások automatikus lezárása (try-with-resource)

Az erőforrások automatikusan lezárásra kerülnek, ha a try-with-resource szerkezetben, AutoCloseable vagy a Closeable interfészt implementáló osztályokat használunk.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

class TryWithResousrce {
 public static void main(String args[]) throws IOException {
  try (BufferedReader br = new BufferedReader(
                           new FileReader("c:/temp/myfile.txt"));
   PrintWriter pw = new PrintWriter(
                    new FileWriter("c:/temp/myfile.txt"))) {
   pw.println("mytext");
   pw.flush();
   System.out.println(br.readLine());
  }
 }
}

4. Multi Catch

A kód duplikáció elkerülése miatt hasznos, amikor ugyanazt a kivétel lekezelő műveletet szeretnénk használni unrelated kivételek esetén, anélkül hogy a közös ősüket kellene elkapnunk.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

class MultiCatch {
 public static void main(String args[]) {
  try {
   BufferedReader br = 
    new BufferedReader(new FileReader("myfile.txt"));
   Connection con = DriverManager
    .getConnection("jdbc:postgresql://myhost/db","u","p");
  } catch (IOException | SQLException ce) {
   //handle exceptions
  }
 }
}

5. Precise ReThrow

Nem kapunk fordítási hibát annak ellenére, hogy a kivétel lekezelő ágban feldobott e objektum általánosabb, mint a throws-nál deklarált FileNotFoundException, mivel a fordító képes meghatározni, hogy a kivétel aktuális típusa ebben az esetben FileNotFoundException lesz.
import java.io.FileNotFoundException;

public class PreciseReThrow {

 public static void main(String args[]) 
                                throws FileNotFoundException {
  try {
   throw new FileNotFoundException();
  } catch (Exception e) {
   throw e;
  }
 }
}

6. Diamond operátor használata

Az olvashatóság megkönnyítése érdekében, a jobb oldalról elhagyhatjuk a generikus típus paramétereket a <> operátor kiírásával.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class DiamondUsage {
 public static void main(String args[]) {
  List<String> list1 = new ArrayList<>();
  List<Map<Integer,List<Double>>> list2 = new ArrayList<>();
 }
}
Persze a lista koránt sem teljes, érdemes még megismerni a NIO 2, a Concurrency API és a JDBC 4.1 újdonságait is mert hasznosak lehetnek a munkánk során és az 1Z0-804 vizsgán is kérdezhetik! .)