Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Felietony/inne Inne Inżynieria oprogramowania

Najnowsza wersja Javy – Java 8

Cześć! Cieszę się, że mnie odwiedziłeś/aś. Zanim przejdziesz do artykułu chciałbym zwrocić Ci uwagę na to, że ten artykuł był pisany kilka lat temu (2014-04-18) miej więc proszę na uwadzę że rozwiązania i przemyślenia które tu znajdziesz nie muszą być aktualne. Niemniej jednak zachęcam do przeczytania.

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:

j8

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

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

0 komentarzy
Inline Feedbacks
View all comments