Kamil Lolo

Kamil Lolo

https://klolo.github.io

programista

projektant

scrum master

technical leader

trener

devops

administrator

Java

javascript

https://kariera.comarch.pl/oferty-pracy/

http://sdacademy.pl/

O czym będzie mowa?

lambda

anonymous class

method reference

effective final

functional interface

Stream

filter

map

flatMap

peek

Intellij

parallel

collect

Predicate

Consumer

Function

primitive stream

terminal operator

intermediate operator

byte code

Wyrażenia lambda

wszystko o czym powinieneś wiedzieć

Single abstract method interface

  • na pewno używaliście jakiegoś interfejsu z tylko jedną metodą abstrakcyjną, przykładowo: Runnable, Callable, Comparator
  • problem klas anonimowych
  • boilerplate
  • czy kompilator nie może domyślić się pewnych rzeczy na nas?
    public static void main(String... args) {
        List<String> names = Arrays.asList("Mateusz", "Zosia", "Ania");

        names.sort(new Comparator<String>() {
            @Override
            public int compare(final String s1, final String s2) {
                return s1.compareTo(s2);
            }
        });
    }

    public interface Comparator<T> {
        int compare(T o1, T o2);
    }

Nadmiarowe informacje w implementacji tej klasy

    public static void main(String... args) {
        List<String> names = Arrays.asList("Mateusz", "Zosia", "Ania");

        names.sort(new Comparator<String>() {
            @Override
            public int compare(final String s1, final String s2) {
                return s1.compareTo(s2);
            }
        });
    }

    public interface Comparator<T> {
        int compare(T o1, T o2);
    }

lista zawiera obiekty typu string

i tylko takie mogę tutaj sortować

dostaje dwa parametry typu T

zwracam int

jedyna ważna linijka

Co zostanie kiedy je usuniemy?

  Comparator<String> comparator = new Comparator<String>() {
            @Override
            public int compare(final String s1, final String s2) {
                return s1.compareTo(s2);
            }
        };
Comparator<String> comparator = (s1, s2) { return s1.compareTo(s2); }
Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);

Jaki byte code wygeneruje kompilator dla klasy anonimowej?

public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Main$1
       3: dup
       4: invokespecial #3                  // Method Main$1."<init>":()V
       7: astore_1
       8: return
}
final class Main$1 implements java.util.Comparator<java.lang.String> {
  Main$1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int compare(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: aload_2
       2: invokevirtual #2                  // Method java/lang/String.compareTo:(Ljava/lang/String;)I
       5: ireturn

  public int compare(java.lang.Object, java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #3                  // class java/lang/String
       5: aload_2
       6: checkcast     #3                  // class java/lang/String
       9: invokevirtual #4                  // Method compare:(Ljava/lang/String;Ljava/lang/String;)I
      12: ireturn
}

Jaki byte code wygeneruje kompilator dla klasy anonimowej?

  • Na poziomie byte code dostepne są instrukcje:
    • invokestatic
    • invokevirtual
    • invokeinterface
    • invokespecial
  • Klasa anonimowa kompilowana jest do osobnego pliku
  • Classloader odpowiada za wczytanie tej klasy podczas startu programu
  • Dużo klas anonimowych = dużo klas do wczytania i trzymania w pamięci

Jaki byte code wygeneruje kompilator dla wyrażenia lambda?

  void printElements(java.util.List<java.lang.String>);
    Code:
       0: aload_1
       1: getstatic     #2       // Field java/lang/System.out:Ljava/io/PrintStream;
       4: dup                    // *duplicate the value on top of the stack
       5: invokevirtual #3       // Method java/lang/Object.getClass:()Ljava/lang/Class;
       8: pop                    // *discard the top value on the stack

       9: invokedynamic #4,  0   // InvokeDynamic #0:accept:(Ljava/io/PrintStream;)Ljava/util/function/Consumer;

      14: invokeinterface #5, 2  // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
      19: return
  • invokedynamic pozwala na natywne wywoływanie metod pozbawionych informacji o typach argumentów
  • Instrukcja powstała z myślą o dynamicznie typowanych językach jvm, w celu pozbycia się z nich powolnej refleksji
  • Wykorzystywana w JRuby, Nashorn
// 2
void printElements(List<String> strings) {
    strings.forEach(System.out::println);
}

// 1
void printElements(List<String> strings) { 
    strings.forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });
}

Jaki byte code wygeneruje kompilator dla wyrażenia lambda?

BootstrapMethods:
  0: #35 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
        (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
         Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;
         Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
    Method arguments:
      #36 (Ljava/lang/Object;)V
      #37 invokestatic Main.lambda$main$0:(Ljava/lang/String;)V
      #38 (Ljava/lang/String;)V
private static void lambda$1(String item){ //generated by Java compiler
  System.out.println(item);
}

private static CallSite bootstrapLambda(Lookup lookup, String name, MethodType type){ //
  // lookup = provided by VM
  // name = "lambda$1", provided by VM
  // type = String -> void
  MethodHandle lambdaImplementation = lookup.findStatic(lookup.lookupClass(), name, type); 
  return LambdaMetafactory.metafactory(lookup, "accept", 
    //signature of lambda factory
    MethodType.methodType(Consumer.class), 
    //signature of method Consumer.accept after type erasure 
    MethodType.methodType(void.class, Object.class),  
    //reference to method with lambda body
    lambdaImplementation, 
    type); 
}

void printElements(List<String> strings){
  Consumer<String> lambda = invokedynamic(#bootstrapLambda)
  strings.forEach(lambda);
}

Jaki byte code wygeneruje kompilator dla wyrażenia lambda?

private static CallSite cs;

void printElements(List<String> strings){
  Consumer<String> lambda;

  //begin invokedynamic
  if(cs == null)
    cs = bootstrapLambda(
            MethodHandles.lookup(), 
            "lambda$1", 
            MethodType.methodType(void.class, String.class)
         );

  lambda = (Consumer<String>) cs.getTarget().invokeExact();
  //end invokedynamic

  strings.forEach(lambda);
}

Strategie "odsładzania" kodu z lambdy

class A {
    public void foo() {
        List<String> list = ...
        list.forEach(s -> { System.out.println(s); });
    }
}
class A {
    public void foo() {
        List<String> list = ...
        list.forEach( [lambda for lambda$1 as Block] );
    }

    static void lambda$1(String s) {
        System.out.println(s);
    }
}

stateless lambda

class A {
    public void foo() {
        List<String> list = ...
        list.forEach(new Consumer<String>() {
                    @Override
                    public void accept(String s) {
                        System.out.println(s);
                    }
                }
        );
    }
}

Strategie "odsładzania" kodu z lambdy

class B {
    private int top = ...

    public void foo() {
        List<Person> list = ...
        final int bottom = ...
        list.removeIf( p -> (p.size >= bottom && p.size <= this.top) );
    }
}
class B {
     private int top = ...

    public void foo() {
        List<Person> list = ...
        final int bottom = ...
        list.removeIf( [ lambda for lambda$1 as Predicate capturing (bottom, top) ]);
    }

    boolean lambda$1(int bottom, Person p) {
        return (p.size >= bottom && p.size <= this.top;
    }
}

lambdas capturing immutable values

Dlaczego akurat invokedynamic?

There are a number of ways we might represent a lambda expression in bytecode, such as inner classes, method handles, dynamic proxies, and others. Each of these approaches has pros and cons. In selecting a strategy, there are two competing goals: maximizing flexibility for future optimization by not committing to a specific strategy, vs providing stability in the classfile representation.

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

Dozwolone formy wyrażenia lambda

names.sort((s1, s2) -> { return s1.compareTo(s2); });

names.sort((s1, s2) -> s1.compareTo(s2));
 
new Thread(() -> System.out.println("hello from another thread")).start();

foo( param -> System.out.print(param)); 

Runnable r = () -> System.out.println();

// Lambda to tylko implementacja interfejsu z jedna metodą abstrakcyjna
Callable<String> callable = new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "some string";
    }
  };

Callable<String> callable = () -> "some string";
    

Difference between Lambda Expression and Anonymous class

 

One key difference between using Anonymous class and Lambda expression is the use of this keyword. For anonymous class ‘this’ keyword resolves to anonymous class, whereas for lambda expression ‘this’ keyword resolves to enclosing class where lambda is written.

- Viral Patel

 

Lambda i "this"

public class Demo {
    
    ...

    @Override
    public String toString() {
        return "demo";
    }
}
    public static void main(final String... args) {
        final Demo demo = new Demo();
    }
   Demo() {
        // 1. com.comarch.training.Demo$1@69991479
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(this);
            }
        })
        .start();

        // 2. demo
        new Thread(() -> System.out.println(this)).start();
    }

Functional interface

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}


@FunctionalInterface
interface MyFI {
    // ERROR: Multiple non-overriding abstract methods found in interface
    void method1();
    void method2();
}

Ograniczenia: Interfejs to interfejs

    static abstract class F {
        public abstract void foo();
    }

    public static void main(String... args) {
        //Error: Target type of lambda conversion must be an interface
        F f = () -> {}; 
    }

Effective final

    static String lastName = "";

    public static void main(String... args) throws InterruptedException {
        String userName = "";

        Runnable r1 = () -> lastName = "Henio";

        // ERROR: local variables referenced from a lambda 
        // expression must be final or effectively final
        Runnable r2 = () -> userName = "Henio";
        
        new Thread(r).start();
        Thread.sleep(100);
        System.out.println(lastName);
    }

Pułapki Javy 8

  final String httpRequestMethod = httpRequest.getMethod();
        Response response = httpRequest.getBody()
                .map(body -> requestBuilder.method(
                                    httpRequestMethod,
                                    entity(body, APPLICATION_JSON_TYPE))
                              )
                .orElse(requestBuilder.method(httpRequestMethod));
if (body.isPresent()) {
    return requestBuilder.method(httpRequestMethod, entity(body.get(), APPLICATION_JSON_TYPE)
} else {
    return requestBuilder.method(httpRequestMethod)
}
  • Proste rozwiązania można łatwo skomplikować
  • Clean code mówi o maksymalnej liczbie lini w metodzi 30, co jeżeli teraz możemy zapisać w jednej linijce więcej rzeczy? Łatwiej złamać SRP
  • Nie ćwicz nowości na systemie produkcyjnym 

Referencje do metod


    private static interface IMagicAlgorithm {
        long calculate(long x);
    }

    private static long calculate(long x) {
        return x * x;
    }

    public static void main(String... args) throws InterruptedException {
        // obliczenia zawarte w lambda - slabe do utrzymania
        IMagicAlgorithm algorithm1 = x -> x * x;
        
        // wywolanie istenijacej metody - nadal nadmiarowy kod
        IMagicAlgorithm algorithm2 = x -> calculate(x);
        
        // referencja do metody!
        IMagicAlgorithm algorithm3 = Demo::calculate;
    }

-lambda tworzy anonimową metodę, co w przypadku kiedy mam już metodę i chcemy ją wykorzystać?

-wisienka na torcie lambd

 

 

Referencje do metody z klasy zewnętrznej

public class Demo {

    @FunctionalInterface
    private static interface IMagicAlgorithm {
        long calculate(long x);
    }

    private long calculate(long x) {
        return x * x;
    }

    private class Inner {
        public void foo() {
            // obliczenia zawarte w lambda - slabe do utrzymania
            IMagicAlgorithm algorithm1 = x -> x * x;

            // wywolanie istenijacej metody - nadal nadmiarowy kod
            IMagicAlgorithm algorithm2 = x -> calculate(x);

            // referencja do metody!
            IMagicAlgorithm algorithm3 = Demo.this::calculate;
        }
    }
}

Możliwe wersje referencji do metod

  • Klasa::metodaInstancji
  • Klasa::metodaStatyczna
  • obiekt::metodaInstancji
  • Klasa::new
  • typ[]::new

 

    public void foo() {
        final char[] numbers = {'A', 'b', 'C'};
        final String result = Stream.of(numbers)
                .map(String::valueOf) // metoda statyczna
                .map(String::toLowerCase) //  metoda instancji ( s-> s.toLowerCase() )
                .map(this::appendeSeparator) //  metoda obiektu
                .map(WorkshopTest::trimmer) //  metoda obiektu
                .collect(Collectors.joining());
        System.out.println(result);
    }

    private static String trimmer(final String input) {
        return input.trim();
    }

    private String appendeSeparator(final String input) {
        return input + "-";
    }

Instead of using
AN ANONYMOUS CLASS


you can use
A LAMBDA EXPRESSION


And if this just calls one method, you can use
A METHOD REFERENCE

Kilka słów o programowaniu

FUNKCYJNYM

 

deklaratywne vs imperatywne

 

 

Programowanie

Deklaratywne

Zestaw operacji jest  nich przedstawiany bez ujawniania tego jak są zaimplementowane. Mówimy co ma być zrobione a nie jak.

Imperatywne

Kod zmienia stan programu, korzystamy z zmiennych. Musimy określić nie tylko co ma być zrobione ale również jak. 

users.forEach(System.out::print);
// nie interesuje mnie czy 
// w metodzie jest for,
// for-each czy while
// mowie co ma być zrobione
for (int i = 0; i < users.size(); i++) {
    System.out.println(users);
}

Co to jest programowanie funkcyjne?

 

 

Czym jest programowanie funkcyjne?

Programowanie gdzie najważniejszą rolę pełnią funkcje oraz abstrakcyjne ujęcie przepływu sterowania i operacji na danych za pomocą funkcji aby uniknąć efektów ubocznych i ograniczyć modyfikację stanu aplikacji.

Czy się charakteryzuje FP?

  • Method as first citizen
  • Pure functions
  • Function composition
  • Lazy evaluation
  • ...

Funkcje w programowaniu funkcyjnym są obywatelami pierwszej klasy a co za tym idzie:

  • Funkcje można przekazywać jako parametr do innych funkcji
  • Funkcje można przypisywać do zmiennych jak obiekty klas.
  • Funkcje mogą tworzyć funkcje i je zwracać
    static Function<String, String> initUserName = (param) -> "userName2:" + param;
    
    private static String getLassName(final Function<String, String> callback) {
        final Function<String, String> afterFn = element -> element.toLowerCase();
        return callback
                .andThen(afterFn)
                .apply(" Kowalski");
    }

Pure functions

  • funkcja otrzymuje wszystkie konieczne dane jako parametry
  • brak odowłania do zewnętrznych stanów
  • dla danego zestawu danych wynik jest taki sam
  • funkcja nie ma efektów ubocznych (np. nie zmienia stanu aplikacji)
  • Czyste funkcje ułatwiają testowanie, wielowątkowe programowanie, cachowanie wyników operacji

Function composition

Składanie funkcji pozwala nam budować z funkcji, które pełnią rolę cegiełek, pełnowartościowe aplikacje. 

public class Demo {
    private static final BigDecimal USDToPLNRate = new BigDecimal("3.86");
    private static final BigDecimal amountOfDollar1 = new BigDecimal("23");
    private static final BigDecimal amountOfDollar2 = new BigDecimal("43");

    @FunctionalInterface
    private interface ExchangeRateConverter extends BiFunction<BigDecimal, BigDecimal, BigDecimal> {
        default Function<BigDecimal, BigDecimal> curryRate(BigDecimal t) {
            return u -> apply(t, u);
        }
    }

    public static void main(final String... args) {
        final Function<BigDecimal, BigDecimal> converter = ((ExchangeRateConverter) BigDecimal::multiply)
                .curryRate(USDToPLNRate) // currying - rozwijanie funkcji
                .andThen(Demo::afterConversion)
                .compose(Demo::beforeConversion);

        converter.apply(amountOfDollar1);
        converter.apply(amountOfDollar2);
    }

    private static BigDecimal beforeConversion(BigDecimal amount) {
        System.out.println("Kwota przed konwersja:" + amount);
        return amount;
    }

    private static BigDecimal afterConversion(BigDecimal amount) {
        System.out.println("Po konwersji:" + amount);
        return amount;
    }
}

Lazy evaluation

 

Kiedy wykona się kod z poprzedniego przykładu?

Najważniejsze interfejsy funkcyjne

Interfejsy funkcyjne dostępne w JDK

interfejs parametry zwraca opis metoda inne metody
Runnable - void uruchamia logikę bez parametrów i wyniku run -
Supplier - T Dostarcza wartość typu T get -
Consumer T void Pobiera wartość typu T accept andThen
Function T R Funkcja z parametrem typu T apply compose,
andThen,
identity
UnaryOperator T
 
T Operator jednoargumentowy dla typu T apply compose,
andThen,
identity
BinaryOperator T,T T Operator dwuargumentowy dla typu T apply andThen, maxBy, minBy
Predicate T boolean Funkcja zwracająca wartość logiczną test and, or,
negate, isEqual

Dostępne są również interfejsy z dwoma generycznymi parametrami:

BiConsumer, BiFunction, BiPredicate, BinaryOperator

Stream API

jako inversion of control

Czym jest strumień?

  • sekwencja elementów danego typu na których wykonujemy operacje
  • przetwarza dane na żądanie (lazy), nie przechowuje danych
  • pozwala agregować wynik operacji
  • operacje mogą być łączone w łańcuch
  • pozwala na odwrócenie kontroli
  • inspirowany konstrukcjami używanymi w językach funkcyjnych

Jak utworzyć strumień?

// 1
final int[] data = {1, 2, 3};
Arrays.stream(data).forEach(System.out::print);

// 2
final List<String> strings = Arrays.asList("a","b");
strings.stream().forEach(System.out::print);

// 3
Stream.of("a","b").forEach(System.out::print);

// 4
Stream.generate(strings.iterator()::next)
    .limit(strings.size())
    .forEach(System.out::print);

// 5
IntStream.range(0,3).forEach(System.out::print);

// 6
Stream.empty().forEach(System.out::print);

// 7 
Stream<String> streamBuilder =
  Stream.<String>builder().add("a").add("b").add("c").build();

Strumienie typów prostych

// tworzy strumień intów
IntStream intStream = IntStream.range(1, 3); 

// strumień intów łącznie z liczbami krańcowymi
LongStream longStream = LongStream.rangeClosed(1, 3); 

// Tworzy strumień DoubleStream
Random random = new Random();
DoubleStream doubleStream = random.doubles(3); 

// dodatkowe metody arytmetyczne
int[] numbers = {2,4,23,21};
Arrays.stream(numbers).average().ifPresent(System.out::println); // 12.5
  • IntStream, LongStream, DoubleStream
  • Strumienie te są dużo bardziej wydajne niż używanie strumienia z wrapperami
  • strumienie typów prostych mają również wbudowane operacje arytmetyczne np. sum, max, min, average etc.
  • W celu zamiany strumienia obiektów na strumień typu prostego można skorzystać z metod: mapToInt/Long/Double

Operacje terminujące i natychmiastowe

Wszystkie operacje jakie możemy wykonywać na strumieniu dzielą się na natychmiastowe zwracają one referencje do strumienia i nie wykonują żadnej logiki. Dzięki czemu strumienie są leniwe. Drugi rodzaj operacji jaki możemy wykonać na strumieniu to operacja terminująca strumień która zwraca najczęściej jakiś obiekt wynikowy i wymusza wykonanie wszystkich operacji w strumieniu. 

// 1
holdings
                .stream() // utworzenie strumienia
                .sorted() // operacja natychmiastowa
                .distinct() // operacja natychmiastowa
                .count(); // operacja terminująca

// 2
final List<String> names = Arrays.asList("Anna", "Aneta", "Zosia", "Henia");
final Stream<String> userStream = names.stream();

userStream.forEach(System.out::println);

//java.lang.IllegalStateException: stream has already been operated upon or close
userStream.forEach(System.out::println); 

najważniejszych metod

interfejsu Stream

10

1. forEach

 

 

 

  • promuje efekty uboczne w funkcjach
  • wykonuje podaną operacje (Consumer) na każdym elemencie strumienia
  • operacja terminująca
  • nie ma gwarancji zachowanie kolejności w wielowątkowym strumieniu
  • metoda forEachOrdered działa analogicznie, jednak zachowuje kolejność przy wielowątkowym przetwarzaniu
final String[] balances = {"0,01", "19.99", "100 "};

// z wykorzystanie referncji do metody
Arrays.stream(balances).forEach(System.out::println); 

// z wykorzystaniem wyrazenia Lambda
Arrays.stream(balances).forEach(current -> System.out.println(current) ); 
 void forEach(Consumer<? super T> action);

2. filter

 

  • Pozwala odfiltrować elementy strumienia. Usuwając z nich te dla których wywołanie predicate ma wartość false.
  • Pierwotna kolekcja nie ulega zmianie.
  • Operacja natychmiastowa

 

 

final List<String> names = Arrays.asList("Anna", "Aneta", "Zosia", "Henia");
        names.stream()
                .filter(name -> name.startsWith("A"))
                .forEach(System.out::println);  // operacja terminująca
Stream<T> filter(Predicate<? super T> predicate)

3. map

 

 

  • Metoda konwertuje obiekt z jednego typu do innego.
  • Ilość elementów w strumieniu po takiej operacji jest taka sama, jednak obiekty zostają zamienione na inny typ.
  • Operacja natychmiastowa
  • Jako parametr przyjmuje obiekt typu Function

 

 

        final String[] balances = {"0,01", "19.99", "100 "};

        Arrays.stream(balances)
                .map(String::trim) // .map( current -> current.trim() )
                .map(balance -> balance.replace(",", "."))
                .map(BigDecimal::new) // .map( current -> new BigDecimal(current) )
                .forEach(System.out::println);
<R> Stream<R> map(Function<? super T, ? extends R> mapper)

4. limit

 

 

 

  • ogranicza liczbę elementów strumienia do podanej liczby
  • operacja natychmiastowa
  • dla strumienia wielowątkowego operacja jest kosztowna
  • jeżeli chcemy szybszego działania wielowątkowego  to musimy wyłączyć ograniczenie trzymania kolejności metodą: unordered
  • przydatne w nieskończonych strumieniach

 

 

final Random random = new Random();
IntStream
        .generate(random::nextInt)
        .limit(3)
        .forEach(System.out::println);
Stream<T> limit(long maxSize);

5. flatMap

 

 

  • zamienia każdy element strumienia na zero lub więcej elementów
  • operacja natychmiastowa
  • dostępne również takie metody jak: flatMapToInteger, flatMapToDouble, flatMapToLong

 

 

class Book {
    private String name;
    ...
}

class Library {
    private String name;
    private List<Book> books;
    ...
}

final List<Library> libraries = Arrays.asList(
    new Library( "publiczna", Arrays.asList(new Book("Java for dummies"))),
    new Library( "uniwersystecka", Arrays.asList(new Book("Thinking in Java")))
);
libraries.stream()
        .filter(LibraryFilters::onlyPublic)
        .flatMap(library -> library.books.stream())
        .forEach(System.out::println);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

6. collect

 

 

 

Jest to metoda terminująca pozwalająca przepakować dane ze strumienia w  obiekt docelowy używając przy tym dodatkowej logiki. Na wejściu przyjmuje obiekt klasy Collector. Zbiór predefiniowanych Collectorów znajduje się w klasie Collectors.

 

 

<R, A> R collect(Collector<? super T, A, R> collector);

<R> R collect(Supplier<R> supplier,
               BiConsumer<R, ? super T> accumulator,
               BiConsumer<R, R> combiner);
public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
}

Najważniejsze collectory

 

 

 

Factory method Opis
toList() Zamienia dane ze strumienia w liste
toSet() Zamienia dane ze strumienia w set
toCollection() Pozwala zamienić dane na kolekcje danego typu. Jako parametr podajemy referencje do kontruktora
toMap() Pozwala zmapować strumień do mapy. Wymaga podania funkcji mapującej klucz (najczęściej wykorzystywana to Function.identity()) oraz mapującej wartość
joining() Pozwala na łączenie Stringów. Można podać separator, prefix, postfix
counting() Zlicza elementy jako long
summarizingDouble() Dostarcza takich statystyk jak: średnia, min, max, liczba, suma. Do każdych w tych danych są również osobne metody.
groupingBy() Grupuje dane do listy
partitioningBy() Grupuje dane po podanym predykacie

collect - przykłady

// 1. Wypisze: ArrayList
List<String> filteredCountries1 = countries.collect(Collectors.toList()); 
System.out.println(filteredCountries1.getClass()); 

// 2. Zwróci obiekt LinkedList
countries.collect(Collectors.toCollection(LinkedList::new)); 

// 3. Zwróci: Set<String> 
countries.collect(Collectors.toSet()); 

// 4. Zwróci: Map<String, String>
countries.collect(toMap(Function.identity(), String::toUpperCase));

// 5. Wypisze: AfganistanTurkmenistan
countries.collect(joining());
System.out.println(counrtyStr); 

// 6. Wypisze: Afganistan,Turkmenistan
countries.collect(joining(","));
System.out.println(counrtyStr2); 

// 7. Wypisze "->Afganistan,Turkmenistan."
String counrtyStr3 = countries.collect(joining(",","->","."));
System.out.println(counrtyStr3);

7. reduce

 

 

 

  • łączy wszystkie elementy strumienia w jeden wyjściowy obiekt

 

 

 

myStream.reduce("", (s1, s2) -> s1 + s2); // 1. brak kompilacji do StringBuilder

myStream.collect(Collectors.joining()); // 2. korzysta z StringBuildera

myStream.collect( // 3
        StringBuilder::new, 
        StringBuilder::append, 
        StringBuilder::append
);
Optional<T> reduce(BinaryOperator<T> accumulator);   

T reduce(T identity, BinaryOperator<T> accumulator);

<U> U reduce(U identity, 
             BiFunction<U, T, U> accumulator, 
             BinaryOperator<U> combiner);

reduce vs collect

  • obie metody na pierwszy rzut oka robią to samo
  • służą do zamiany elementów strumienia w jeden obiekt wyjściowy
  • reduce przeznaczony dla obiektów nie mutowalnych, redukcja dla każdego elementu strumienia tworzy nowy obiekt wyjściowy
  • collect redukuje obiekty mutowalne, obiekt wyjściowy tworzony jest jednorazowo

8. groupingBy

 

 

 

 

Metoda pozwala wykonać operację grupowania elementów strumienia do mapy podobną do GROUP BY z SQL za pomocą funkcji klasyfikującej. Dodatkowo metoda występuje w wersji wielowątkowej: groupingByConcurrent

 

 

// kod imperatywny:
Map<String, List<Employee>> result = new HashMap<>();
for (Employee e : employees) {
  String city = e.getCity();
  List<Employee> empsInCity = result.get(city);
  if (empsInCity == null) {
    empsInCity = new ArrayList<>();
    result.put(city, empsInCity);
  }
  empsInCity.add(e);
}
// kod deklaratywny
Map<String, List<User>> usersByFirstName = 
    getUsers()
        .stream()
        .collect(groupingBy(User::getFirstName));
<T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier);


<T, K, A, D> Collector<T, ?, Map<K, D>> 
    groupingBy(Function<? super T, ? extends K> classifier,
               Collector<? super T, A, D> downstream)


<T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> 
    groupingBy(Function<? super T, ? extends K> classifier,
               Supplier<M> mapFactory,
               Collector<? super T, A, D> downstream)

groupingBy

 

 

 

 

Metoda GroupingBy jest dostepna w 3 wersjach:

  • z funkcją klasyfikującą
  • z funkcją klasyfikującą i Collectorem
  • z funkcją klasyfikującą, Collectorem, z metodą Supplier zwracającą mapę

 

 

 

 

// 1. Mapa z setem postow gdzie kluczem jest typ
Map<BlogPostType, Set<BlogPost>> postsPerType 
    = posts.stream().collect(groupingBy(BlogPost::getType, toSet()));

// 2. mapa postow danego autora gdzie posty sa jako mapa per typ
Map<String, Map<BlogPostType, List>> map 
    = posts.stream().collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));

// 3. mapa post gdzie wartoscia jest suma polubien
Map<BlogPostType, Integer> likesPerType 
    = posts.stream().collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));

// 4. Uzycie metody joining
Map<BlogPostType, String> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));

9. partitioningBy

 

 

 

 

  • pozwala rozdzielić elementy strumienia do dwóch grup na podstawie przekazanego predykata
  • jako drugi parametr można podać Collector za pomocą które złączymy elementy tych dwóch grup

 

 

        // 1
        Map<Boolean, List<Person>> adult = persons
                .stream()
                .collect(
                    partitioningBy(
                        person -> person.getAge() > 18
                    )
                );

        // 2
        Map<Boolean, Set<String>> adult = persons
                .stream()
                .collect(
                        partitioningBy(
                            person -> person.getAge() > 18,
                            mapping(Person::getName, toSet())
                        )
                );

10. Parallel stream

  • Java pozwala przetwarzać strumienie w wielu wątkach jednocześnie
  • korzysta z ForkJoinPool
  • pozwala szybciej procesować duże zbiory danych
  • należy uważać na wydajność przy niektórych operacjach
  • z zwykłego strumienia przechodzimy do równoleglego za pomocą metody parallel, wracamy o sekwencyjnego za pomocą sequential
  • tam gdzie dostępne są metody do tworzenia strumienia (stream) są również metody do tworzenia strumienia wielowątkowego.
        final String[] countries = {"Polska", "Malta", "Afganistan", "Turkmenistan"};
        final Predicate<String> isEnoughtLenght = country -> country.length() > 6;
        
        Arrays.stream(countries)
                .parallel()
                .peek(it -> System.out.println(it + " w watku: " + Thread.currentThread().getName()))
                .filter(isEnoughtLenght)
                .count();
-----------------------------------------------------------
Wypisze:
Polska w watku: ForkJoinPool.commonPool-worker-3
Turkmenistan w watku: ForkJoinPool.commonPool-worker-2
Malta w watku: ForkJoinPool.commonPool-worker-1
Afganistan w watku: main

Jak działa parallel stream?

  • domyślnie wykorzystywany jest ForkJoinPool który jest wspólny dla całego jvm
  • domyślnie ForkJoinPool tworzy tyle wątków ile jest procesów na maszynie
  • wprowadzony w Java 7
  • ForkJoinPool.commonPool()
  • -Djava.util.concurrent.ForkJoinPool.common.parallelism=5
   int count = 0;

    public void bu() {
        IntStream.range(0, 10_000)
                .parallel()
                .forEach(i -> count++);

        System.out.println("\n count = " + count); // 7433, 9195
    }
   AtomicInteger count = new AtomicInteger(0);

    @Test
    public void bu() {
        IntStream.range(0, 10_000)
                .parallel()
                .forEach(i -> count.incrementAndGet());

        System.out.println("\n count = " + count); // 10000
    }

Czytelność strumieni

Co robi ten kod legacy?

Czy dostaniemy ArrayIndexOutOfBoundsException?

  public static String fooWithoutStream() {
        final List<String> result = new LinkedList<>();
        String nameList = "";
        for (int i = 0; i < holdings.size(); i++) {
            for (int j = 0; j < holdings.get(i).getCompanies().size(); ++i) {
                for (int k = 0; k < holdings.get(i).getCompanies().get(j).getUsers().size(); k++) {
                    User user = holdings.get(i).getCompanies().get(j).getUsers().get(k);
                    if (!result.contains(user.getFirstName())) {
                        result.add(user.getFirstName());
                    }
                }
            }
        }

        result.sort(new Comparator<String>() {
            @Override
            public int compare(final String o1, final String o2) {
                return o1.compareTo(o2);
            }
        });

        for (String name : result) {
            nameList += name + " ";
        }

        return nameList.trim();
    }

Czytelność strumieni

   public static String fooWithStream() {
        return holdings.stream()
                .flatMap(holding -> holding.getCompanies().stream())
                .flatMap(company -> company.getUsers().stream())
                .map(User::getFirstName)
                .distinct()
                .sorted()
                .collect(Collectors.joining(" "));
    }
  public static String fooWithoutStream() {
        final List<String> result = new LinkedList<>();
        String nameList = "";
        for (int i = 0; i < holdings.size(); i++) {
            for (int j = 0; j < holdings.get(i).getCompanies().size(); ++i /** blad */) {
                for (int k = 0; k < holdings.get(i).getCompanies().get(j).getUsers().size(); k++) {
                    User user = holdings.get(i).getCompanies().get(j).getUsers().get(k);
                    if (!result.contains(user.getFirstName())) {
                        result.add(user.getFirstName());
                    }
                }
            }
        }

        result.sort(new Comparator<String>() {
            @Override
            public int compare(final String o1, final String o2) {
                return o1.compareTo(o2);
            }
        });

        for (String name : result) {
            nameList += name + " ";
        }

        return nameList.trim();
    }

Kiedy nie używać

strumieni?

Kiedy nie używać strumieni?

  • w krytycznych wydajnościowo miejscach gdzie parallel Stream nie sprawdzi się
  • kiedy strumień to przerost formy nad treścią
  • kiedy nasz kod nie działa do końca i trzeba go dokładnie debugować
List<Integer> list = Arrays.asList(1, 2, 3);
 
// Old school
for (Integer i : list)
    for (int j = 0; j < i; j++)
        System.out.println(i * j);
 
// "Modern"
list.forEach(i -> {
    IntStream.range(0, i).forEach(j -> {
        System.out.println(i * j);
    });
});

Debugowanie

STRUMIENI

Metoda peek

 

 

 

  • wykonuje na każdym elemencie podaną logikę
  • jest operacją natychmiastową
  • jeżeli korzystamy z wielowątkowości należy zapewnić synchronizację
  • ta metoda powstała z myślą o tym żeby ułatwić debuggowanie strumieni
  • przyjmuje jako parametr obiekt typu Consumer

 

 

Stream
    .of("one", "two", "three", "four")
    .filter(e -> e.length() > 3)
    .peek(e -> System.out.println("Filtered value: " + e))
    .map(String::toUpperCase)
    .peek(e -> System.out.println("Mapped value: " + e))
    .collect(Collectors.toList());

Intellij evaluate

 

 

 

Java stream debugger

Intellij plugin

Jak nabrać wprawy w używaniu strumieni?

https://github.com/klolo/java8-stream-free-exercises

Koniec

 

Czas na pytania

https://slides.com/kamillolo/better-late-than-never

Linkografia

  • grafiki pochodzą z https://static.pexels.com, http://freepik.com
  • przystępne materiały z Javy 8: http://winterbe.com/
  • opis programowania funkcyjnego: http://wazniak.mimuw.edu.pl/index.php?title=Programowanie_funkcyjne/Wstęp
  • http://www.deadcoderising.com/2017-06-13-why-pure-functions-4-benefits-to-embrace-2/
  • http://2.bp.blogspot.com/-Mgh0WScPjW8/Td-8b0ha7UI/AAAAAAAAADg/rs9FJhh2jIY/s1600/karate_01.jpg
  • https://fthmb.tqn.com/dApe-GPqGqxCLoha4Mi-lIjoODo=/4728x3549/filters:no_upscale():fill(transparent,1)/about/bank-vault-door-ajar-digital-10185347-5748d1015f9b58516518ae95.jpg
  • https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
  • https://www.flaticon.com/

Get a Taste of Lambdas and Get Addicted to Streams

https://www.youtube.com/watch?v=1OpAgZvYXLQ

 

Design Patterns in the Light of Lambda Expressions

https://www.youtube.com/watch?v=e4MT_OguDKg

 

Refactoring to Functional Style with Java 8

https://www.youtube.com/watch?v=wjF1WqGhoQI&t=3131s

Przydatne linki video

Better late than never – full version (90m)

By Kamil Lolo

Better late than never – full version (90m)

Temat dla każdego, kto słyszał o nowościach jakie zagościły jakiś czas temu w Javie, ale nie miał jeszcze okazji przyjrzeć się im z bliska. Zaczniemy od wyrażeń lambda, tego jak działają pod maską i dlaczego dzięki nim możemy w Javie zacząć myśleć funkcyjnie. Jednak wiadomo, że nie samą lambdą człowiek żyje. Pokażę również jak potężnym narzędziem są strumienie. Dzięki nim można tworzyć kod szybciej niż kiedykolwiek, skupiając się na tym, co ma robić, a nie w jaki sposób. Przez podstawowe operacje takie jak filtr, map dojdziemy do wielowątkowego przetwarzania i collectorów. Jak w tym wszystkim pomaga nasz ulubieniec Intellij? Sprawdzimy też jak można refaktorować stary kod używając najnowszych elementów języka oraz jak debugować strumienie. Ostrzegam jednak, że kiedy napiszecie swój pierwszy kod z wykorzystaniem strumieni, już nigdy nie będziecie pisać inaczej.

  • 683