Java 7/8/9

workshops beyond legacy code

 

Kamil Lolo

Czego możecie się spodziewać?

  • Przypomnienia rzadziej używanych elementów Javy 7
  • Omówienia nowości z Javy 8 i ich przećwiczenia
  • Wspomnienie najważniejszych nowości Javy 9
  • Omówienia teorii i dużej ilości ćwiczeń
  • Testy jednostkowe
  • 7 minut Przerwy co 53 minuty

Dlaczego warto o tym mówić i to ćwiczyć?

Dlaczego Java 8?

  • zwiększenie naszych kompetencji
  • unikanie "boilerplate code"
  • skupienie się na tym co ma robić nasz kod a nie jak
  • pisanie mniejszej ilości kodu w celu osiągnięcia tego samego efektu
  • efektywnie korzystanie z języka
  • możliwość używania nowych wzorców
  • Java 8 jest szybsza od poprzednich wersji out-of-box
  • nowe narzędzia do rozwiązywania problemów
  • minimalizacja ilości błędów
  • żeby nie pisać kodu Javy 1.4 korzystając z kompilatora Javy 1.8

Java 7

...czyli zapomniane elementy języka

try-resources

Podczas używania JDBC można spotkać się z problemem połączeń do bazy danych, które zawsze trzeba pamiętać zamknąć na końcu w bloku 

finally.  Java 7 wprowadza mechanizm który pozwala wyeliminować ten boilerplate.

Connection con = null;
try {
    con = ConnectionUtil.getDBConnection();
    callProcedure("FOO_PROCEDURE", con);
}
catch (SQLException e) {
    e.printStackTrace();
}
finally {
    if(con !=null) {
      try {
        con.close();
      } 
      catch(Exception e) {

      }
    }
}

try-resources

Podczas używania JDBC można spotkać się z problemem połączeń do bazy danych, które zawsze trzeba pamiętać zamknąć na końcu w bloku 

finally.  Java 7 wprowadza mechanizm który pozwala wyeliminować ten boilerplate.

Connection con = null;
try {
    con = ConnectionUtil.getDBConnection();
    callProcedure("FOO_PROCEDURE", con);
}
catch (SQLException e) {
    e.printStackTrace();
}
finally {
    if(con !=null) {
      try {
        con.close();
      } 
      catch(Exception e) {

      }
    }
}
try (final Connection con = ConnectionUtil.getDBConnection()) {
    callProcedure("FOO_PROCEDURE", con);
}
catch (SQLException e) {
    e.printStackTrace();
}

try-resources

mechanizm pozwala definiować dowolną liczbę zmiennych które mają zostać zamknięte. Oddziela się je średnikiem.  Jeżeli metoda close ma throws to musimy wyjątek złapać i obsłużyć. 

static class ClosableUnicorn implements AutoCloseable {
    void magic() {
        System.out.println("magic");
    }

    @Override
    public void close() throws IOException {
        System.out.println("on close");
    }
}

public static void main(final String... args) {
    try (final ClosableUnicorn unicorn = new ClosableUnicorn()) {
        unicorn.magic();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
}

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

try-resources in Java 9

Java 9 wprowadza możliwość wykorzystywania zmiennych w bloku try-resources które są effective final. 

  void foo() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(""));

        try (br) {
            br.readLine();
            //br = null; ERROR
        }

        //br = null; ERROR
    }

    void doSomethingWith(Connection connection) throws Exception {
        try(connection) {
            connection.doSomething();
        }
    }

abstract method in enum

W enumach można definiować metody abstrakcyjne.  (od jakiej wersji?)

W jaki sposób taką metodę można zaimplementować?

enum Employe {
    JUNIOR,
    REGULAR,
    CHEF;
    public abstract long getSalary();
}

abstract method in enum

    enum Employe {
        JUNIOR(500) {
            public float getSalary() {
                return baseSalary;
            }
        },
        REGULAR(1000) {
            public float getSalary() {
                return baseSalary + (REGULAR_BONUS * baseSalary);
            }
        }, 
        CHEF(2000) {
            public float getSalary() {
                return baseSalary + (CHEF_BONUS * baseSalary);
            }
        };

        final static float REGULAR_BONUS = 0.3f;

        final static float CHEF_BONUS = 0.5f;

        final long baseSalary;

        Employe(final long baseSalary) {
            this.baseSalary = baseSalary;
        }

        public abstract float getSalary();
    }

underscore in numbers

Java 7 wprowadza możliwość używania podkreśleń podczas wpisywania literałów liczb. Jednak od Javy 9 nie będzie można używać podkreślenia jako nazwy zmiennej. 

    long bigNumber = 1_000_000; // correct in Java 7+

    long _ = 1000; // incorect in Java 9 

diamond

Podczas deklaracji zmiennych generycznych przed Java 7 obowiązkowe było podanie po obu stronach znaku równa się typu jakiego ma być zmienna. Informacja ta jednak może być wywnioskowana na podstawie kontekstu przez kompilator i już nie jesteśmy zmuszeni do wpisywania tej nadmiarowej informacji, wystarczy wpisać "<>". Pominięcie operatora diamentu spowoduje że kompilator uzna zmienną za surowego typu i nie będziemy mogli skorzystać z dobrodziejstw generyków. 

List<String> names = new ArrayList<String>();
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();

// można zastosować skrócony zapis
Map<String, List<Integer>> map = new HashMap<>();

multi-catch

Często w metodach realizujących logikę biznesową  dochodzi do sytuacji gdzie nasz kod może wyrzucić kilka różnych wyjątków, a my je procesujemy w taki sam sposób. Wymaga to od nas napisania kilku bloków catch. Java 7 wprowadziła mechanizm który potrafi nam tutaj ułatwić życie. 

   try {
        foo();
   }
   catch(final IOException e) {
        processError(e);
   }
   catch(final MethodNotFoundException e) {
        processError(e);
   }
   catch(final IllegalArgumentException e) {
        processError(e);
   }

multi-catch

Często w metodach realizujących logikę biznesową  dochodzi do sytuacji gdzie nasz kod może wyrzucić kilka różnych wyjątków, a my je procesujemy w taki sam sposób. Wymaga to od nas napisania kilku bloków catch. Java 7 wprowadziła mechanizm który potrafi nam tutaj ułatwić życie. 

   try {
        foo();
   }
   catch(final IOException e) {
        processError(e);
   }
   catch(final MethodNotFoundException e) {
        processError(e);
   }
   catch(final IllegalArgumentException e) {
        processError(e);
   }
   try {
        foo();
   }
   catch(final IOException | MethodNotFoundException | IllegalArgumentException  e) {
        processError(e);
   }

string in switch

Od Javy 7 można używać w wyrażeniach switch typów String. 

public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
     String typeOfDay;
     switch (dayOfWeekArg) {
         case "Monday":
             typeOfDay = "Start of work week";
             break;
         case "Tuesday":
         case "Wednesday":
         case "Thursday":
             typeOfDay = "Midweek";
             break;
         case "Friday":
             typeOfDay = "End of work week";
             break;
         case "Saturday":
         case "Sunday":
             typeOfDay = "Weekend";
             break;
         default:
             throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
     }
     return typeOfDay;
}

Java 8

...czyli wszystko co jest teraz sexy

Lambda

i functional interface

Single abstract method interface (SAM 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 usunę?

    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);
            }
        });

       names.sort(
            // nasza pierwsza lambda!
            (final String s1, final String s2) -> {
                return s1.compareTo(s2);
            }
        );

    }

 

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.

 

Lambda i "this"

public class Demo {

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

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

    @Override
    public String toString() {
        return "demo";
    }

}

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);
    }

Debuggowanie

Pułapki Javy 8

  final String httpRequestMethod = httpRequest.getMethod();
        Response response = httpRequest.getBody()
                .map(body -> requestBuilder.method(
                                    httpRequestMethod,
                                    Entity.entity(body,MediaType.APPLICATION_JSON_TYPE))
                              )
                .orElse(requestBuilder.method(httpRequestMethod));
if (body.isPresent()) {
    return requestBuilder.method(httpRequestMethod,Entity.entity(body.get(),MediaType.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

Zmiany w interfejsach

Default

  • Od Javy 8 można w interfejsach implementować metody za pomocą słowa kluczowego default
  • Mechanizm nie powinien być nadużywany. Może prowadzić do problemu diamentu znanego z wielodziedziczenia
  • Default pomaga rozszerzać interfejsy bez łamania kompatybilności z istniejącym kodem
    public interface Vehicle {
        default void print() {
            System.out.println("I am a vehicle!");
        }
    }

    public interface FourWheeler {
        default void print() {
            System.out.println("I am a four wheeler!");
        }
    }

    public class Car implements Vehicle, FourWheeler {
        public void print() { //bez tej implementacji kompilator zglosi błąd
            Vehicle.super.print();
        }
    }

Statyczne metody i pola

W interfejsach można również deklarować metody statyczne oraz pola które z domysłu są finalne i statyczne. Odwołujemy się do tego jak do pól, metod statycznych w klasach

   public interface Vehicle {
        String broomSound = "pffff";

        static void broomBroom() {
            broomSound =""; // error
            System.out.println(broomSound); // dostep w metodzie statycznej
        }
    }

Execute around method

następca wzorca template method

definicja problemu

  • wzorzec szablon metody wymaga od nas utworzenia klasy bazowej oraz osobnej klasy dla każdej implementacji. Co czasami jest przerostem formy na treścią
  • w kodzie mamy wiele bardzo podobnych metod, przykładowo:
    public Properties foo(final Properties input) throws Exception {
        logger.info("start, with params: {}", input);
        final Connection dbConnection = getDBConnection();
        Properties result = new Properties();

        try {
            validateInput(input);
            result = callDbProcedure(input, dbConnection);

            if(result.getProperty("error") != null) {
                throw new Exception(result.getProperty("error"));
            }

        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println("Rolling back...");
            dbConnection.rollback();
        } finally {
            dbConnection.close();
        }

        logger.info("end");
        return result;
    }
    public void shoulDoSomething(final Properties input ) throws Exception {
        runInTransaction( connection-> {
            validateInput(input);
            return callDbProcedure(input, connection);
        });
    }
    public Properties runInTransaction(final Function<Connection, Properties> callback) throws Exception {
        logger.info("start, with params: {}", input);
        final Connection dbConnection = getDBConnection();
        Properties result = new Properties();

        try {
            result = callback.apply(dbConnection);

            if(result.getProperty("error") != null) {
                throw new IllegalStateException(result.getProperty("error"));
            }

        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println("Rolling back...");
            dbConnection.rollback();
        } finally {
            dbConnection.close();
        }

        logger.info("end");
        return result;
    }

Functional programming

...czyli wszystko co jest teraz sexy

Deklaratywne programowanie vs imperatywne

 

 

Deklaratywne

Zestaw operacji jest  nich przedstawiany bez ujawniania tego jak są zaimplementowane i jak wygląda przepływ danych. 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

Method as first citizen

Co byście przekazali jako parametr takiej metody?

    static class UserFromService {
        private final String userName;

        public UserFromService(final String userName) {
            this.userName = userName;
        }

        public String getUserName() {
            return userName;
        }
    }
    
    public static void main(final String... args) {
        final UserFromService userFromService = new UserFromService("Kamil");
        final Properties data = new Properties();

        // funkcja jak pobrac pole userName z obiektu userFromService 
        // i jeżeli nie jest null to wrzucić do Properties
        // pod podanym kluczem.
        setIfNotNull(????);
    }

Method as first citizen

W językach funkcyjnych funkcje występują na tych samych zasadach co obiekty innych typów. 

    public static void main(final String... args) {
        final UserFromService userFromService = new UserFromService("Kamil");
        final Properties data = new Properties();

        setIfNotNull(userFromService::getUserName, data::put, "firstName");
    }

    public static <T> void setIfNotNull( final Supplier<T> getter, 
                                         final BiConsumer<String, T> setter, 
                                         final String key ) {
        final T t = getter.get();

        if (null != t) {
            setter.accept(key, t);
        }
    }

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ć
   private static final 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

Inpure functions depend on some form of external state

Given the same input, the function always result in the same output

4 najważniejsze korzyści ze stosowanie czystych funkcji

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
BiConsumer T,U void Pobiera wartość typu T i U accept andThen
Function T R Funkcja z parametrem typu T apply compose,
andThen,
identity
BiFunction T,U R Funkcja z parametrem typu T i U apply andThen
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
BiPredicate T,U boolean Dwuargumentowa funkcja zwracająca wartość logiczną  test and, or,
negate

Stream API

jako rodzaj IoC

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
  • Strumienie są generyczne, stąd też nie mogą przechowywać typów prostych, lecz ich otoczki
  • Java posiada wbudowane strumienie opakowujące typów prostych w ich wrappery.
  • Strumienie te są dużo bardziej wydajne niż używanie strumienia z wrapperami
  • Obsługiwane są 3 typu takich strumieni: IntStream, LongStream, DoubleStream
  • strumienie typów prostych mają również wbudowane operacje arytmetyczne np. sum, max, min, average etc.

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); 

interfejs Stream

forEach/forEachOrdered

 

 

 

  • 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) ); 

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)

mapToInt/Long/Double

 

 

  • Zamienia strumień Obiektów na strumień prymitywów
  • Pozwala uniknąć nie potrzebnego boxingu
  • Używamy wszędzie tam gdzie robimy mapowanie na typ prosty
        final Human[] people = {new Human(18), new Human(25)};

        Arrays.stream(people)
                // .map(Human::getAge) uzycie tego spowoduje zbedny boxing to Integer!!
                .mapToInt(Human::getAge)
                .forEach(System.out::println);


        class Human {
            int age;
    
            Human(int age) {
                this.age = age;
            }
    
            public int getAge() {
                return age;
            }
        }

flatMap

 

 

  • zamienia każdy element strumienia na zero lub więcej elementów (dokładnie rzecz ujmując na strumień obiektów)
  • operacja natychmiastowa
  • dostępne również takie metody jak: flatMapToInteger, flatMapToDouble, flatMapToLong
  • przyjmuje na wejściu: Stream<R> flatMap(Function<T,  Stream<R> mapper)

 

 

    class Book {
        private String name;

        public Book(final String name) {
            this.name = name;
        }
        
        // getter/setter
    }

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

        public Library(
                    final String name, 
                    final List<Book> books ) {
            this.name = name;
            this.books = books;
        }

        // getter/setter
    }
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()
        .flatMap(library -> library.books.stream())
        .forEach(System.out::println);

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)

distinct

 

 

  • operacja natychmiastowa
  • zwraca strumień z którego usunięto duplikaty
  • wykorzystuje metodę equals do porównania obiektów
  • mało wydajny w wielowątkowym przetwarzaniu. Lepiej przełaczyć się na jeden wątek za pomocą metody sequential

 

 

final List<Library> libraries = Arrays.asList(
    new Library("publiczna",
        Arrays.asList(
            new Book("Java for dummies"),
            new Book("Thinking in Java")
            )
    ),
    new Library("uniwersystecka", 
            Arrays.asList(
            new Book("Thinking in Java"))
    )
);

libraries.stream()
        .flatMap(
            library -> library.books.stream()
         )
        .distinct()
        .forEach(System.out::println);
class Book {
    private String name;

    public Book(final String name) {
        this.name = name;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || 
            getClass() != o.getClass()) 
        return false;

        final Book book = (Book) o;

        return name != null ? 
            name.equals(book.name) 
                : book.name == null;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }
}

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());

sorted

 

 

  • sortuje strumień zgodnie z naturalnym porządkiem, elementy strumienia muszą implementować Comperable
  • w przypadku kiedy klasa nie implementuje Comperable możemy jako parametr przekazać Comparator
  Arrays.asList("Hania","Marian", "Stefan", "Amadeusz", "Franek")
                .stream()
                .sorted()
                .sorted(Comparator.reverseOrder())
                .sorted((o1, o2) -> o1.compareTo(o2))
                .forEach(System.out::println);

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);

skip

 

 

 

  • pomija pierwsze n elementów strumienia
  • jeżeli elementów do pominięcia jest mniej niż elementów strumienia to zwracany jest pusty strumień
  • operacja tania na sekwencyjnym strumieniu
  • w wielowątkowych strumieniach należy użyć metody generate bądź wyłączyć constraint na kolejność obiektów (unordered)

 

 

        String[] countries = {"poland", "russia", "france" , "germany"};
        Arrays.stream(countries)
                .skip(1)
                .forEach(System.out::println);

collect

 

 

 

Jest to metoda terminująca pozwalająca przepakować dane ze strumienia w jakiś 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. 

 

 

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()/Long/Int 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

    private Stream<String> getFilteredCountryStream(final String[] countries) {
        final Predicate<String> isEnoughtLenght = country -> country.length() > 6;
        return Arrays.stream(countries).filter(isEnoughtLenght);
    }

    final String[] countries = {"Polska", "Malta", "Afganistan", "Turkmenistan"};

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

        // 2
        final List<String> filteredCountries2 = getFilteredCountryStream(countries)
                                                    .collect(toCollection(LinkedList::new));

        // 3
        final Set<String> countriesSet = getFilteredCountryStream(countries).collect(toSet());

        // 4
        final Map<String, String> counrtyMap = getFilteredCountryStream(countries)
                                                    .collect(toMap(Function.identity(), String::toUpperCase));

        // 5
        final String counrtyStr = getFilteredCountryStream(countries).collect(joining());
        System.out.println(counrtyStr); // AfganistanTurkmenistan

        // 6
        final String counrtyStr2 = getFilteredCountryStream(countries).collect(joining(","));
        System.out.println(counrtyStr2); // Afganistan,Turkmenistan

        // 7
        final String counrtyStr3 = getFilteredCountryStream(countries).collect(joining(",","To sa kraje:","."));
        System.out.println(counrtyStr3); // To sa kraje:Afganistan,Turkmenistan.

reduce

 

 

 

  • operacja redukcji łączy wszystkie elementy strumienia w jeden wyjściowy obiekt
  • możliwe wersji operacji:
  1. z parametrem BiFunction (funkcja z dwoma parametrami)
  2. z dodatkowym parameterem początkowym 
  3. z parametrem BinaryOperator - combiner używany w wielowątkowym przetwarzaniu, do scalania wyników. Nie używany przez sekwencyjny strumień.

 

 

 

myStream.reduce("", (s1, s2) -> s1 + s2); // brak kompilacji do StringBuilder
myStream.collect(Collectors.joining()); // korzysta z StringBuildera
myStream.collect(
        StringBuilder::new, 
        StringBuilder::append, 
        StringBuilder::append
);

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

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

 

 

+----------+------------+-----------------+
| Name     | City       | Number of Sales |
+----------+------------+-----------------+
| Alice    | London     | 200             |
| Bob      | London     | 150             |
| Charles  | New York   | 160             |
| Dorothy  | Hong Kong  | 190             |
+----------+------------+-----------------+
// 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<Employee>> employeesByCity = employees.stream().collect(groupingBy(Employee::getCity));

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: [", "]"))));

partycjonowanie

 

 

 

 

  • 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())
                        )
                );

min/max

 

 

 

  • znajduje w strumieniu najmniejszy/największy element korzystając z przekazanego komparatora

 

 

        String[] countries = {"poland", "Albania", "france", "germany"};

        Arrays.stream(countries)
                .skip(1)
                .min(String::compareTo) // Albania
                .ifPresent(System.out::println); // prawidlowe uzycia optional

anyMatch/allMatch/noneMatch

 

 

 

  • metody przyjmują Predicate jako parametr i sprawdzają kolejno czy jakikolwiek element strumienia pasuje do niego, czy wszystkie pasują oraz czy żaden nie pasuje
        String[] countries = {"poland", "Albania", "france", "germany"};

        Arrays.stream(countries)
                .skip(1)
                .anyMatch(item -> item.startsWith("Z")); // false

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

ForkJoinPool

  • 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()
  • zmiana liczby wątków poprzez parametr:
    -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

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nec metus justo. Aliquam erat volutpat.

   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);
    });
});

Optional

Optional jako sposób na null pointer

Optional jest to klasa z pakietu java.util, która  przechowuję jakąś wartość w środku. Tą wartością może być null, stąd też Optional z założenia ma pomagać unikać błędów typu NullPointerException. 

Klasa jest generyczna, stąd też może przechowywać dowolnego typu zmienną. 

Gdzie NIE używać Optionali?

  • w polach klas
  • w kolekcjach
  • jako parametr metod
  • w serializowanych polach
    // 1
    Map<String, Optional<List<Optional<User>>>> usersInCompany; // łot !?

    // 2
    class User {
        private final String firstName;

        User(final String firstName) {
            this.firstName = firstName;
        }
        
        public Optional<String> getFirstName() {
            return Optional.of(this.firstName); // OK!
        }
        
    }

Jak nie używać Optional?

// 1
Optional<String> optString = Optional.of(x);

// 2        
if (optString.isPresent()) {
    foo(optString.get());
}

// 3
optString.ifPresent(this::foo);

Metody klasy Optional

static Optional empty() Metoda fabrykująca pustego optionala.
static  Optional of(T value) Metoda fabrykująca Optionala z podaną wartością. Jeżeli przekazany obiekt jest nullem to otrzymamy nullpointer.
static  Optional  ofNullable(T value) Jak wyżej, ale bez null pointera
Optional(T value) Konstruktor również wymaga żeby parametr nie był nullem
T get() Pobranie wartości przechowywanej przez Optional
boolean isPresent() Sprawdzenie czy obiekt  w Optional ma wartośc null
void ifPresent(Consumer  consumer) Wykonanie logiki w przypadku kiedy w Optional znajdują się dane.
filter, map, flatMap Metody analogiczne jak w stream
​<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) Wyrzuca wyjątek jeżeli pole w Optional ma wartość null
​T orElseGet(Supplier  other) Pobiera domyślną wartość z przekazanej metody
T orElse(T other) Przekazujemy domyślną wartość która zostanie zwrócona w przypadku kiedy Optional nie przechowuje danych

Optional w akcji

public char firstChar(String s) {
    if (s != null && !s.isEmpty())
        return s.charAt(0);
    else
        throw new IllegalArgumentException();
}


Optional.ofNullable(s)
    .filter(s -> !s.isEmpty())
    .map(s -> s.charAt(0))
    .orElseThrow(IllegalArgumentException::new);

Optional w akcji

    private String foo(final Holding h) {
        if (h != null) {
            final Company c = h.getCompanies().get(0);
            if (c != null && c.getUsers() != null) {
                final User u = c.getUsers().get(0);
                if (u != null && u.getFirstName() != null) {
                    final String result = u.getFirstName();
                    if (result.length() > 0) {
                        return result;
                    }
                }
            }
        }

        return "404-not-found";
    }

Co robi ta funkcja? 

Optional w akcji

    private String getCompanyFirstUserName1(final Holding holding) {
        if (holding != null) {
            final Company company = holding.getCompanies().get(0);
            if (company != null && company.getUsers() != null) {
                final User user = company.getUsers().get(0);
                if (user != null && user.getFirstName() != null) {
                    final String result = user.getFirstName();
                    if (result.length() > 0) {
                        return result;
                    }
                }
            }
        }

        return "not found";
    }

    private String getCompanyFirstUserName2(final Holding holding) {
        return Optional.ofNullable(holding)
                .map(Holding::getCompanies)
                .map(Vector::firstElement)
                .map(Company::getUsers)
                .map(Vector::firstElement)
                .map(User::getFirstName)
                .filter(name -> name.length() > 0)
                .orElse("not found");
    }

Nashorn

...pomówmy chwilę o javascript

Czym jest Nashorn?

  • Nashorn jest silnikiem js (podobnie jak V8 znany z Node.js ) który rozszerza możliwości Javy pod kątem uruchomienia kodu js przez jvm.
  • Java posiada REPL (ang. read-eval-print loop) dla tego silnika  o nazwie jjs dostępny w JDK
  • Kod Javy można wywoływać z poziomu js oraz w drugą stronę
  • Nashorn wspiera ECMAScript 5.1

Wywoływanie funkcji js w Java

Nashorn pozwala wywoływać funkcje js z poziomu kodu Javy. Do takich funkcji można nawet przekazać obiekty Javy jako parametry. 

var fun1 = function(name) {
    print('Hi there from Javascript, ' + name);
    return "greetings from javascript";
};

var fun2 = function (object) {
    print("JS Class Definition: " + 
    Object.prototype.toString.call(object));
};
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));

Invocable invocable = (Invocable) engine;

Object result = invocable.invokeFunction("fun1", "Peter Parker");
System.out.println(result);
System.out.println(result.getClass());

// Hi there from Javascript, Peter Parker
// greetings from javascript
// class java.lang.String

Wywoływanie funkcji Java w js

Z poziomu kodu js możemy wywoływac kod javy. Do klas Javy odwołujemy się wtedy poprzez: Java.type. 

var MyJavaClass = Java.type('my.package.MyJavaClass');

var result = MyJavaClass.fun1('John Doe');
print(result);

// Hi there from Java, John Doe
// greetings from java
static String fun1(String name) {
    System.out.format("Hi there from Java, %s", name);
    return "greetings from java";
}

Trochę mniejsze nowości

Zmiany w Date API

 

Java 9

...o tym co nas czeka za chwilę

Czy Java 9 jest gotowa

do używania na produkcji?

Not yet.

  • problemy z Maven/Gradle
  • dobre wsparcie IDE
  • masa problemów na które jeszcze nie ma odpowiedzi w sieci

jshell

 

moduły

zmiany w interfejsach

Nowe metody w klasie Optional

Java 9 uzupełnia Optional o kilka przydatnych metod

List<String> userPermits = ???
List<String> defaultPermit = Arrays.asList("transfer","login","history");

// 1
Optional
    .ofNullable(users)
    .stream() // NOWOŚĆ!
    .forEach(this::activatePermitOnDatabase)

// 2 - wykorzystanie domyslnej wartosci
Optional
    .ofNullable(users)
    .orElse(Arrays.asList("hania","ela"))
    .stream() // stream z klasy List
    .forEach(this::activatePermitOnDatabase)

public Stream<T> stream()

public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);

public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

Ćwiczenia

wejdźmy w świat streamów

Zasady obowiązujące podczas ćwiczeń

 

  • NIE używamy pętli, if-ów, switchy
  • NIE używamy zmiennych, tylko stałe
  • NIE wysyłamy kodu innym osobom, jeżeli chcemy komuś pomóc to siadamy obok i jesteśmy jego driverem (nie dotykamy klawiatury)

Bank system

 

  in Java 8

KATA

programistyczne

Linkografia

  • grafiki pochodzą z https://static.pexels.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

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

Koniec

 

Czas na podsumowanie zdobytej wiedzy i wasze opinie.

Java 7/8/9

By Kamil Lolo

Java 7/8/9

  • 1,691