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
wszystko o czym powinieneś wiedzieć
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);
}
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
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);
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
}
invokestatic
invokevirtual
invokeinterface
invokespecial
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
// 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);
}
});
}
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);
}
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);
}
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);
}
}
);
}
}
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;
}
}
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
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";
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
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();
}
@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();
}
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 = () -> {};
}
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);
}
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)
}
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
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;
}
}
}
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 + "-";
}
Kilka słów o programowaniu
FUNKCYJNYM
Zestaw operacji jest nich przedstawiany bez ujawniania tego jak są zaimplementowane. Mówimy co ma być zrobione a nie jak.
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);
}
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.
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");
}
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?
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
jako inversion of control
// 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();
// 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
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);
interfejsu Stream
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);
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)
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)
final Random random = new Random();
IntStream
.generate(random::nextInt)
.limit(3)
.forEach(System.out::println);
Stream<T> limit(long maxSize);
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);
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();
}
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 |
// 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);
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);
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)
Metoda GroupingBy jest dostepna w 3 wersjach:
// 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: [", "]"))));
// 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())
)
);
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
-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
}
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();
}
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();
}
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);
});
});
STRUMIENI
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 plugin
https://github.com/klolo/java8-stream-free-exercises
Czas na pytania
https://slides.com/kamillolo/better-late-than-never
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