Guava VS JDK 8. Porównanie podobnych funkcjonalności.

Dzisiaj chciałbym napisać o bibliotece Guava i o równorzędnych narzędziach które zostały wprowadzone do JDK 8. Do napisania tego postu skłonił mnie głównie fakt że niewiele projektów z którymi mam styczność w pracy zawodowej jest napisana w JDK 8.Najpierw odrobina informacji.

Guava to open source-owa biblioteka pisana przez programistów Google, która posiada bardzo profesjonalnie narzędzia umożliwiające pisanie czystego i wydajnego kodu. Narzędzia te umożliwiają lub usprawniają poniższe aspekty pracy kodera:

  • metody obsługi kolekcji,
  • cash-owanie,
  • wspomaganie typów prymitywnych,
  • wielowątkowość,
  • adnotacje,
  • działania na stringach,
  • I/O,
  • walidacje.

Ciekawostką jest to że Google pomimo tego że udostępniło bibliotekę to raczej nie przyjmuje kontrybucji z zewnątrz. Guava powstała tuż po powstaniu JDK 5 i była ciągle rozwijana aż do dzisiaj. Obecnie jej wersja ma numer 19 jednak twórcy Javy również nie próżnowali i wiele elementów z Guavy znalazło swoje odzwierciedlenie w JDK 8.

Dla tych którzy nie mieli jeszcze styczności z JDK 8 na początek szybkie wprowadzenie do lambd i interfejsów funkcyjnych żeby mieli jasność co dzieje się w kodzie przedstawianym w przykładach:

  • Functional Interface - jest to interfejs posiadający tylko jedną metodę abstrakcyjną ,może jednak posiadać dowolną ilość metod domyślnych oraz metody abstrakcyjne przesłaniające metody z klasy java.lang.Object. Instancje interfejsu tego rodzaju możemy tworzyć w lambdzie. Istnieje też adnotacja (@FunctionalInterface) z tym że wspomaga nas ona tylko odrobinę wskazując kompilatorowi że oznaczony nią interfejs jest funkcyjny i powinien spełniać jego kryteria,a w przeciwnym wypadku należy dać ostrzeżenie. Już wcześniej mieliśmy do czynienia z takimi interfejsami:
    public interface Runnable { void run(); } 
    public interface Callable<V> { V call() throws Exception; } 
    public interface ActionListener { void actionPerformed(ActionEvent e); } 
    public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
    
  • Lambda Expression - jest to zgrabny sposób na stworzenie implementacji interfejsu funkcyjnego. Lambdy możemy przekazywać do metod i dzięki temu tworzymy bardziej przejrzysty kod. Lambdy wspomagają nas w pisaniu programów stosując paradygmaty funkcyjny ( Paradygmat Funkcyjny ). Mały przykład stworzenia implementacji interfejsu funkcyjnego Predicate za pomocą lambdy:
    List<String> names = Arrays.asList(new String[]{"John","Nick", "Jason"});
    
    names.stream()
                  .filter(s -> s.startsWith("J"))
                  .forEach(System.out::println);
    



  1. com.google.common.base.Optional VS java.util.Optional
    "Null sucks." -Doug Lea
    "I call it my billion-dollar mistake." - Sir C. A. R. Hoare, on his invention of the null reference.
    Wartość null może oznaczać wiele - porażkę , sukces, błąd czyli tak naprawdę wszystko i dlatego warto używać typów Optional żeby pisać kod który będzie zrozumiały dla czytającego. Typ Optional jest używana do przedstawiania wartości obiektu jeżeli ta wartość istnieje lub powiadomienie że wartość nie istnieje. Największą jej zaletą jest wymuszenie kontrolowania wartości obiektów i reagowanie na ich brak w miejscach gdzie jest to niedopuszczalne. Takie wymuszenie kontrolowania tego czy coś może mieć wartość lub nie pomaga stworzyć kod który jest bardziej odporny na błędy. Reasumując, używając tego narzędzia otrzymujemy kod który jest o wiele bardziej ekspresywny. Obydwie klasy - ta z Guavy oraz ta JDK 8 są do siebie bardzo podobne.
    GUAVA:
    Integer value1 =  null;
    Integer value2 =  new Integer(10);
    Optional<Integer> optionalValue1 = Optional.fromNullable(value1);
    //Optional<Integer> optionalValue2 = Optional.of(value2);       //This code invoke exception
    Optional<Integer> optionalValue2 = Optional.of(value2);
    Integer valueFromOptionalValue1 = optionalValue1.or(new Integer(0));
    Integer valueFromOptionalValue2 = optionalValue2.get();
    
    JDK 8:
    Integer value1 =  null;
    Integer value2 =  new Integer(10);
    Optional<Integer> optionalValue1 = Optional.ofNullable(value1);
    Optional<Integer> optionalValue2 = Optional.of(value2);
    //Optional<Integer> optionalValue2 = Optional.of(value2);  //This code invoke exception
    Integer valueFromOptionalValue1 = optionalValue1.orElse(new Integer(0));
    Integer valueFromOptionalValue2 = optionalValue2.get();
    optionalValue2.ifPresent(s -> System.out.println(s));
    


  2. com.google.common.base.Joiner VS java.util.StringJoiner
    GUAVA:
    Klasa Joiner służy nam do łączenia Stringów. Jak się jednak domyślasz czytelniku ma ona odrobinę większe możliwości i pozwala na ominięcie wartości null (skipNulls()), zastąpienie null wartością domyślną (useForNull(String)), a także dodanie separatorów i ułatwienie dodawania Stringów pochodzących z jakiejś kolekcji.
    Joiner joiner = Joiner.on("/").skipNulls();
    joiner.join("prefix-","2016","02","26","-suffix");
    
    JDK 8:
    Klasa StringJoiner w JDK 8 pozwala na wszytko to co pozwalał nam jej odpowiednik w Guavie ale posiada też odrobinę więcej przydatnych elementów jak np: dodawanie sufixu, prefixu i separatora w jednej metodzie, a także potrafi współpracować ze stream-ami z JDK 8. Nie posiada metod takich jak skipNulls() z Guavy jednak w łatwy sposób można uzyskać taką funkcjonalność stosując java.util.function.Predicate (o których również w tym poście).
    StringJoiner sj = new StringJoiner("/", "prefix-", "-suffix");
    sj.add("2016");
    sj.add("02");
    sj.add("26");
    System.out.println(sj.toString());
    
    Klasę StringJoiner świetnie się komponuje ze stream-ami:
    String namesJoined = names.stream()
                    .filter(Objects::nonNull)
                    .collect(Collectors.joining(",","[","]"));
    


  3. com.google.common.base.Predicate VS java.util.function.Predicate
    W matematyce o predykacie mówimy że jest to funkcja która przyjmuję zmienną w wyniku dając wartość logiczną true lub false. Klasa Predicate pozwala na zdefiniowanie takiej funkcji i użycie jej np. do filtrowanie elementów kolekcji w celu uzyskania odpowiedzi czy spełniają one założone przez nas (w predykacie) warunki. Predykaty można również łączyć ze sobą za pomocą metod and , or , negate (metody istnieją zarówno w Guava jak i JDK 8)
    GUAVA:
    public class GuavaApp{
    
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean apply(String input) {
                return input.startsWith("J");
            }
        };
    
        public static void main(String[] args) {
            GuavaApp guavaApp = new GuavaApp();
    
            List<String> names = Lists.newArrayList(new String[]{"John","Nick", "Jason"});
            Collection<String> namesFiltered = FluentIterable.from(names)
                    .filter(guavaApp.predicate)
                    .toList();
            System.out.println(Joiner.on(",").join(namesFiltered));
         }
    }
    

    W przykładzie Guavy wykorzystałem zaimplementowany interfejs Predicate do wyboru (podczas filtrowania) imion z kolekcji zawierających literę 'J' na początku. Użyta została również klasa narzędziowa FluentIterable o której powiem później w tym poście.
    JDK 8:
    W standardowej bibliotece Javy używamy klasy Predicate bardzo podobnie jak robiliśmy to w Guav-ie. Interfejs przydaje się w wielu miejscach m.in w stream-ach przy filtrowaniu kolekcji:
    List<Integer> numbers2 = Arrays.asList(new Integer[]{1,2,3,4,5,6,7});
    Predicate<Integer> pred = i -> i%2 == 0;
    numbers2.stream()
            .filter(pred.and(i -> i < 6))
            .forEach(i -> System.out.println(i));
    
    W tym przykładzie użyłem tak naprawdę dwóch implementacji Predicate, pierwszą przypisałem do zmiennej o nazwie pred ,a drugą wrzuciłem za pomocą metody and() i lambdy.


  4. com.google.common.base.Function VS java.util.function.Function
    Jest to interfejs funkcyjny który podobnie jak Predicate przyjmuje parametr ale może zwracać różne typy dzięki temu tworzymy sobie funkcje którą możemy używać jako parametr metody.Interfejs parametryzujemy typem wejściowym oraz wyjściowym (w tej kolejności).
    JDK 8:
    List<Integer> numbers2 = Arrays.asList(new Integer[]{1,2,3,4,5,6,7});
    Function<Integer,String> funct = i -> i%2 == 0;
    numbers2.stream()
            .map(funct)
            .forEach(i -> System.out.println(i));
    
    Przefiltrowanie kolekcji z pomocą Function zdefiniowanym i przypisanym do zmiennejfnct powoduje zmianę typu kolekcji z Integer na String!
    GUAVA :
    public class GuavaApp{
    
        Function<String, Integer> func = new Function<String,Integer>(){
            @Override
            public Integer apply(String input) {
                return input.length();
            }
        };
    
        public static void main(String[] args) {
            GuavaApp guavaApp = new GuavaApp();
    
            List<String> names = Lists.newArrayList(new String[]{"John","Nick", "Jason"});
            Collection<Integer> namesFilteredTransformed = FluentIterable.from(names)
                    .transform(guavaApp.func)
                    .toList();
            System.out.println(Joiner.on(",").join(namesFilteredTransformed));
         }
    }
    
    Kod z pomocą Function Guavy wykonuje tą samą robotę co kod użyciem Function z JDK 8, jedyna różnica to odrobina więcej kodu.


  5. com.google.common.base.Supplier VS java.util.function.Supplier
    Interfejs funkcyjny Supplier służy do zwracania jakiegoś obiektu (metoda get() ) który zostanie stworzony w momencie wywołania tej metody lub w jakiejś akcji która występuje wcześniej. Jest to więc taki lazy loader i swietnie nadaje się do implementacji różnego rodzaju cache.
    GUAVA:
    public class GuavaApp{
    
       Supplier<List<String>> supplier = new Supplier<List<String>>() {
            @Override
            public List<String> get() {
                //costly database query
                return Lists.newArrayList(new String[]{"John Ramob", "Rocky", "Specialist"});
            }
        };
    
        public static void main(String[] args) {
            GuavaApp guavaApp = new GuavaApp();
    
            Supplier<List<String>> lazySupplier = guavaApp.supplier;
    
                //now weh we need the list,costly database query is initiated
            System.out.println(Joiner.on(" | ").join(lazySupplier.get()));
         }
    }
    
    Jak widać w Supplier implementujemy wykonanie jakiegoś kosztownego zapytania do bazy i zostaje ono uruchomione dopiero kiedy tego naprawdę potrzebujemy.
    JDK 8:
    Supplier<List<String>> lazySupplier = () -> Arrays.asList(new String[]{"John Ramob", "Rocky", "Specialist"});
    
    lazySupplier.get().stream()
                    .forEach(s-> System.out.println(s));
    
    W JDK 8 jest to o tyle prostsze że możemy użyć lambdy. Przykłady wykorzystania inteerfejsu Supplier które podałem są bardzo proste ale zachęcam do zapoznania się z ciekawymi przykładami użycia które można znaleźć w internecie.


  6. java.util.Objects VS com.google.common.base.Objects/MoreObjects
    Opisywane klasy to typowe klasy narzędziowe służące do operacji na obiektach. Z jakichś powodów Google wydzieliło w ostatnim czasie niektóre metody do klasy MoreObjects, a w JDK 8 można znaleźć odpowiedniki wszystkich metod w jednej klasie o nazwie Objects. GUAVA:
    Integer value1 =  null;
    Integer value2 =  new Integer(10);
    
    System.out.println(Objects.equal(value1,value2));
    
    //System.out.println(value1.equals(value2));           //This code invoke exception
    
    System.out.println(Objects.hashCode(value1,value2));
    
    System.out.println(MoreObjects.firstNonNull(value1,value2));
    
    JDK 8:
    Integer value1 =  null;
    Integer value2 =  new Integer(10);
    
    System.out.println(Objects.equals(value1,value2));
    //System.out.println(value1.equals(value2));        //This code invoke exception
    
    System.out.println(Objects.hash(value1,value2));
    
    System.out.println(Objects.isNull(value2));
    
    Jak widać obydwie klasy posiadają bliźniacze metody. Ich użycie jest bardzo proste ale jak widać może być bardzo przydatne.


  7. com.google.common.util.concurrent.ListenableFuture VS java.util.concurrent.CompletableFuture
    W standardowym arsenale narzędzi wielowątkowych mieliśmy do tej pory Callable i Future. Future jest to rezultat wywołania asynchronicznego. Rezultat może już istnieć lub wciąż być obliczany gdzieś w odmętach JVM, fajnie by było wykorzystać zwróconą wartość natychmiast po jej otrzymaniu. Z pomocą przychodzi nam ListenableFuture z Guava oraz CompletableFeature z JDK 8.
    JDK 8:
    ExecutorService executors = Executors.newFixedThreadPool(10);
    CompletableFuture<String> future = CompletableFuture
                    .supplyAsync(() -> "42", executors)
                    .thenApply(a -> String.valueOf(Integer.valueOf(a)/2))
                    .exceptionally((t) -> "Exception");
    
    try {
         System.out.print(future.get());
         executors.shutdown();
    } catch (InterruptedException e) {
         e.printStackTrace();
    } catch (ExecutionException e) {
          e.printStackTrace();
         }
    }
    

    GUAVA:
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
    
    ListenableFuture<String> explosion = service.submit(new Callable<String>() {
             public String call() {
                  eturn "BUUUUM";
             }
         });
    
    Futures.addCallback(explosion, new FutureCallback<String>() {
             public void onSuccess(String explosion) {
                 System.out.print(explosion);
             }
             public void onFailure(Throwable thrown) {
                 System.out.print("Shit happens");
             }
         });
    
    service.shutdown();
    }
    
    W obydwóch przypadkach po wykonaniu pracy wątku zostanie uruchomiona metoda którą sobie zdefiniujemy. Jak widać możemy obsłużyć również porażkę wykonania wątku.


  8. com.google.common.collect.FluentIterable VS java.util.stream Stream-y to bardzo ciekawe i przydatne narzędzie które dostajemy w JDK 8, głównym ich celem są operacje na źródłach które umożliwiają operacje agregujące. Odpowiednikiem (w pewnym sensie) w Guavie jest klasa narzędziowa FluentIterable która potrafi skutecznie zastąpić niektóre funkcje ze stream-ów. Szczegółowe różnice można znaleźć w dokumentacji Guavy ( FluentIterable )
    GUAVA:
    public class GuavaApp{
    
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean apply(String input) {
                return input.startsWith("J");
            }
        };
    
        Function<String, Integer> func = new Function<String,Integer>(){
            @Override
            public Integer apply(String input) {
                return input.length();
            }
        };
    
        public static void main(String[] args) {
            GuavaApp guavaApp = new GuavaApp();
    
    
            List<String> names = Lists.newArrayList(new String[]{"John","Nick", "Jason"});
            Collection<String> namesFiltered = FluentIterable.from(names)
                    .filter(guavaApp.predicate)
                    .toList();
            System.out.println(Joiner.on(",").join(namesFiltered));
    
            Collection<Integer> namesFilteredTransformed = FluentIterable.from(names)
                    .filter(guavaApp.predicate)
                    .transform(guavaApp.func)
                    .toList();
            System.out.println(Joiner.on(",").join(namesFilteredTransformed));
    
        }
    }
    

    JDK 8:
    Stream.of("d2", "a2", "b1", "b3", "c")
          .map(s -> {
                     System.out.println("map: " + s);
                     return s.toUpperCase();
                    })
         .filter(s -> {
                      System.out.println("filter: " + s);
                      return s.startsWith("A");
                    })
         .forEach(s -> System.out.println("forEach: " + s));
    

    Należy pamiętać że obydwie klasy działają inaczej i nie można powiedzieć że ich funkcjonalności się pokrywają jednak potrafią skutecznie uprzyjemnić pracę programisty. Ważne jest żeby pamiętać o tym że w Stream-ie każdy element kolekcji przechodzi osobno ścieżkę metod tak jak to by była pętla foreach, a w przypadku FluentIterable kolekcja po wykonaniu działania w jednej metodzie laduje jako jej wynik w kolejnej.

Wiem że bardzo pobieżnie potraktowałem opisywane biblioteki jednak ich pełne wytłumaczenie wykracza poza temat tego postu. Chciałem jedynie pokazać że nawet jeżeli w naszym projekcie nie mamy JDK 8 to jednak wciąż możemy używać Guavy żeby nasz kod był czytelniejszy i jednocześnie wydajniejszy.

Show post

Post podsumowujący konkurs "Daj się poznać 2016"

Konkurs “Daj się poznać 2016” dobiega końca i jest to już mój ostatni post związany z nim. Chciałbym jeszcze raz przytoczyć moje osobiste cele które przyświecały mi kiedy dołączałem do konkursu czyli zwiększenie motywacji do rozwinięcia bloga oraz pisanie większej ilości tekstów i ten cel udało mi się w pełni zrealizować! Sam projekt który rozwijałem (”Social Headquarters”) nie został jeszcze skończony ale dał mi dużo radości i pozwolił poznać lub pogłębić wiedzę na temat takich bibliotek i narzędzi jak Elasticsearch, AngularJS ,Spring Data, Spring MVC. Projekt oczywiście będę cały czas rozwijał ale teraz mam bardzo dużo zajęć w szkole i będzie on musiał poczekać do wakacji.

Mała statystyka mojego udziału w konkursie:

  • Napisanych postów - 22
  • Ilość commit-ów w projekcie - 36
  • Ilość spędzonych godzin nad tworzeniem tego wszystkiego - od groma ;)

Dodatkowo warto wspomnieć że udział w konkursie zmotywował mnie i przeżuciem się w końcu na dobry serwer dedykowany stawiając na nim serwer Nginx z Passanger (Ruby on Rails) oraz rozbudowałem bardzo dużo funkcjonalności w samym blogu który jest pisany w rails-ach (kod bloga)

Na koniec dziękuję twórcy konkursu za jego pomysł i możliwość wzięcia w nim udziału. Serdecznie polecam jego bloga - devstyle//Maciej Aniserowicz . Pozdrawiam każdego kto przeczytał jakiś mój post i zachęcam do dalszego zaglądania tutaj gdyż nie mam zamiaru na tym poprzestać.

Show post