Cześć ;)
Uległem pokusie i ściągnąłem JDK Javy 1.8 :) chciałem potestować, zobaczyć na własne oczy co nowego dodali, pierwszy problem jaki się pojawił – Oracle nie wspiera już Win XP – smutek ogólnie, gdyż ja nadal mam Win XP SP3.. StackOverFlow pomogło to obejść, wystarczy ściągnąć JDK normalnie, 7zipem rozpakować ten plik exe i wrzucić zawartość sobie gdzieś w sensowny folder i uruchomić:
1 | FOR /R %%f IN (*.pack) DO "%JAVA_HOME%\bin\unpack200.exe" -r -v "%%f" "%%~pf%%~nf.jar" |
oczywiście przed tym warto zmienić zmienną JAVA_HOME na odpowiednią dla tego JDK, czyli na folder gdzie skopiowało się to JDK.
Jeśli masz system Win 7 lub wyżej to zainstaluje się normalnie ;)
Następny problem to zmuszenie Eclipse’a do kompilowania kodu używając nowego JDK, aby to zrobić trzeba uruchomić „Install New Software” w menu Help i po prawej na górze kliknąć Add i tam w location wpisać
1 | http://download.eclipse.org/eclipse/updates/4.3-P-builds/ |
a to co wpiszemy w nazwę nie ma znaczenia ;) gdy klikniecie OK to pojawi się
1 | Eclipse Java 8 Support (for Kepler SR2) |
zaznaczamy to i instalujemy.
Tworzymy nowy projekt i prawym klikamy na niego, na dole mamy Properties, po lewej stronie odnajmujemy Java Compiler i upewniamy się że wygląda to tak jak tu:
A skoro działa to możemy przejść do szybkiego review ;)
Zacznijmy od strumieni. Nie są to strumienie takie jak otwieramy do, np. plików. Te strumienie pracują na kolekcjach. Każda kolekcja ma teraz metodę stream() oraz parallelStream(), obie zwracają strumień, z tym że druga robi wszystko żeby przetwarzanie było równoległe – ma to swoje plusy i minusy, przetwarzanie, używające parallelStream() będzie szybsze, ale pewnie będzie to realizowane tak że podzieli kolekcję, przemieli jej części osobno i na koniec połączy i warto o tym pamiętać. Strumień udostępnia nam kilkanaście przydatnych funkcji, np:
- Stream map(Function<? super T,? extends R> mapper)
- Stream<T> filter(Predicate<? super T> predicate)
- Stream<T> sorted()
- Stream<T> sorted(Comparator<? super T> comparator)
- Stream<T> distinct()
- void forEach(Consumer<? super T> consumer)
- Optional<T> reduce(BinaryOperator<T> reducer)
oczywiście, jest ich więcej.
Pierwsza metoda to dobrze znana z innych języków np. Pythona, funkcja map :) czyli wykonanie pewnej czynności dla każdego elementu tablicy. Użyjmy jej może, nie? Ale pierw przygotujmy sobie dane do testów:
1 2 3 4 5 6 7 | ArrayList<Double> ints = new ArrayList<Double>(); for(int i=0;i<100000;i++) ints.add(i*Math.random()); FileOutputStream fos = new FileOutputStream("s"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ints); oos.close(); |
Czyli sporo liczb typu double ;)
Skoro zrobiliśmy swoisty persist danych, to wczytajmy je:
1 2 3 4 5 | ArrayList<Double> numbers; FileInputStream fos = new FileInputStream("s"); ObjectInputStream oos = new ObjectInputStream(fos); numbers = (ArrayList<Double>) oos.readObject(); oos.close(); |
i możemy przystąpić do funkcji map:
1 2 3 4 5 6 7 | numbers = (ArrayList<Double>) numbers.stream().map(new Function<Double, Double>() { @Override public Double apply(Double arg0) { return arg0%400; } }).collect(Collectors.toList()); |
lub korzystając z przetwarzania równoległego:
1 2 3 4 5 6 7 | numbers = (ArrayList<Double>) numbers.parallelStream().map(new Function<Double, Double>() { @Override public Double apply(Double arg0) { return arg0%400; } }).collect(Collectors.toList()); |
Zmierzyłem czas obu rozwiązań :)
stream – 93ms
parallelStream – 47ms
Java udostępnia wyrażenia lambda, a więc powyższy kod możemy skrócić do:
1 | t = (ArrayList<Double>) numbers.stream().map((d) -> (d%400)).collect(Collectors.toList()); |
Co raczej nie wypływa na zmianę wydajności a sam interfejs pełni rolę funkcji anonimowych.
Podobnie możemy użyć metody filter:
1 | t = (ArrayList<Double>) numbers.stream().filter(n -> (n>500)).collect(Collectors.toList()); |
Tutaj używamy interfejsu Predicate, czyli testujemy elementy listy i te które spełniają „zbieramy” metodą collect (była też użyta w przykładzie wcześniej) i zwracamy jako listę, używając do tego Kolektora.
A więc teraz wątek możemy stworzyć np. tak:
1 2 3 4 5 6 7 | new Thread(()-> { int a=6; if(a%2==0) System.out.println("jestem 2 watkiem ;) "); }).start(); |
korzystając dokładnie z tego samego mechanizmu co wcześniej – wyrażeń lambda. Możemy zamienić każdy interfejs który ma dokładnie jedną metodę, dodając mu adnotację:
1 | @java.lang.FunctionalInterface |
na interfejs którego możemy używać w wyrażeniach lambda.
Na koniec kilka przykładów kodu z Java 8:
1 2 3 4 5 6 7 8 9 10 11 12 | public static void test1(ArrayList<Double> numbers){ ArrayList<Double> t; long start = System.currentTimeMillis(); t = (ArrayList<Double>) numbers.stream().distinct().filter(n -> (n<30)).collect(Collectors.toList()); long diff = System.currentTimeMillis()-start; System.out.println(diff+"ms"); start = System.currentTimeMillis(); t = (ArrayList<Double>) numbers.parallelStream().distinct().filter(n -> (n<30)).collect(Collectors.toList()); diff = System.currentTimeMillis()-start; System.out.println(diff+"ms"); } |
Jak sadzicie, co wypisze ten kawałek kodu? Użycie w tym przypadku parallelStream() okaże się zgubne – gdyż distinict() wywala duplikaty co ciężko zrównoleglić a do czasu przeliczania dodaje się wtedy czas przełączania kontekstu, co w efekcie zwiększa czas potrzebny do przeliczenia całości.
Czasy:
Dla stream() – 125ms
Dla parallelStream() – 187ms
Jeśli usuniemy distinict(), czasy przedstawiają się następująca:
stream() – 94ms
parallelStream() – 31ms
Bardzo fajny przykład wykorzystnia możemy tutaj zobaczyć:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Calculator { interface IntegerMath { int operation(int a, int b); } public int operateBinary(int a, int b, IntegerMath op) { return op.operation(a, b); } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition = (a, b) -> a + b; IntegerMath subtraction = (a, b) -> a - b; System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); } } |
jeżeli chcielibyśmy podać jako definicję interfejsu coś więcej niż jedną linijkę to robimy to tak:
1 2 3 | IntegerMath soComplicated = (a,b) -> { return a/2 + b/2; }; |
Podsumowując, Java 8 to krok w stronę języków typu Python. Warto nad nim chwilkę czasu spędzić :)
Mateusz Mazurek