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