Implementujemy własny Collector #19

W ostatnim wpisie poznaliśmy metodę collect i różnego rodzaju gotowe kolektory, które możemy przekazać do tej metody. Korzystaliśmy z tak prostego zapisu jak:

otrzymując z naszego streamu listę elementów. W Java 8 znajdziemy dużo gotowych kolektorów, ale możemy także zaimplementować własny, Zobaczmy jak to zrobić.

Java 8 udostępnia interfejs Collector<T, A, R>, który możemy zaimplementować i opracować własny kolektor. Tak wygląda interfejs Collector:

Parametry:

T – typ, który chcemy poddać działaniu naszego kolektora. Jeśli nasz Stream zawiera Stringi np Stream<String> typem T będzie String

A – jest to pomocniczy typ, który posłuży nam do wewnętrznego przetwarzania typu T. Typem A może być lista np List<String>.

R – jest to typ, który ostatecznie otrzymamy po przetworzeniu naszego streama. Typ R nie musi być identyczny jak typ A. Wywołując nasz kolektor na Stream<String> ostatecznie możemy otrzymać List<Integer>

Może dla ścisłości opiszę to bardziej słownie. Mamy Stream<String> wywołujemy na nim nasz kolektor, który będzie wyglądał tak:

Co oznacza, że pracuje on na elementach typu String – T. Do tego posiada pomocniczą strukturę List<String> – A, na końcu otrzymujemy listę elementów typu List<Integer> – R.

Zajmijmy się teraz opisaniem każdej metody z naszego interfejsu, które będziemy musili zaimplementować po jego implementacji:

    • supplier() – metoda ta zwróci funkcję typu Supplier, która będzie tworzyła nasz pomocniczy accumulator, trzymając się naszego przykładu, funkcja ta może tworzyć nową strukturę typu List:
    • accumulator() – w tej metodzie zwracana jest funkcja typu BiConsumer, która łączy nasz element pochodzący ze streamu na którym pracujemy, tutaj String z akumulatorem, którym jest List<String>.
    • combiner() – łączy dwa akumulatory w całość. Naszym akumulatorem jest List<String>, ta funkcja przyjmuje dwie listy i łączy je w jedną.

      funkcja ta jest przydatna, jeśli nasz stream będzie przetwarzany wielowątkowo. W innym przypadku, metoda ta nie będzie wywoływana. Jeśli nie chcemy, żeby nasz collector był wywoływany wielowątkowo, możemy w tej metodzie po prostu wyrzucić wyjątek.
    • finisher() – w tej metodzie otrzymujemy funkcję, która wytwarza ostateczny wynik naszego kolektora, czyli typ – R. W naszym przypadku będzie to List<Integer>

      Może się zdarzyć, że nasz typ R będzie taki sam jak typ akumulatora – typ A. Wtedy nic nie musimy dodatkowo przetwarzać, a możemy wywołać funkcję statyczną, która po prosty zwróci od razu akumulator:
    • characteristic() – ta metoda służy do konfiguracji naszego kolektora. Gotowe sposoby konfiguracji znajdują się w enumie Collector.Characteristics:
      UNORDERED – kolejność elementów nie jest ważna
      IDENTITY_FINISH – oznaczamy, że metoda finisher() nie musi być wywołana, bo typ A jest taki sam jak typ R.
      CONCURRENT – kolektor może być użyty wielowątkowo
      W naszym przypadku implementujemy tą metodę tak:

      Nie musimy też konfigurować naszego kolektora i można zwrócić:

Ostatecznie nasza implementacja wygląda tak:

Teraz wywołajmy nasz kolektor i zobaczmy jak działa:

Na stworzonej liście stringów wywołujemy nasz nowy kolektor przez:

otrzymujemy listę numerów, która wygląda tak wynik 🙂

Cały kod znajdziecie na githubie.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *