Secrets of

Kamil Lolo

LAMBDA

Kamil Lolo

https://klolo.github.io

programista

javascript

scrum master

technical leader

trener

devops

administrator

Java

javascript

kung fu

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

O czym będzie mowa?

Lambda

klasa anonimowa

byte code

clousure

asembler

serializacja

JIT

historia lambdy

Java 10/11

invoke dynamic

functional interface

type erasure

method reference

stream

debug

effective final

clean code

Co to jest

lambda

?

In computer programming, an anonymous function (function literal, lambda abstraction, or lambda expression) is a function definition that is not bound to an identifier.

 

Paulo Lambdelho

// 1
function getValue(x) { return x * x; }
alert(getValue(10));

// 2
alert(function(x) { return x * x; });

// 3
alert((x => x*x)(10));

Przykład lambdy w...js

Historia lambdy w pigułce

  • 1936 Alonzo Church opracowuje rachunek lambd
  • 1958 lambdy pojawiają się w języku Lisp
  • 1996 JDK 1.0.2
  • 2007 lambda pojawia się w C#
  • 2014 lambda pojawia się w Java
(define (adder n) (lambda (x) (+ x n)))

Historia lambdy w pigułce

Jak to działa?

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

lista zawiera obiekty typu string

i tylko takie mogę tutaj sortować

dostaje dwa parametry typu T

zwracam int

jedyna ważna linijka

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

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

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

Co znajduje się w pierwszych bajtach każdego pliku class?

Co znajduje się w pierwszych bajtach każdego pliku class?

Class files are identified by the following 4 byte header (in hexadecimal): CA FE BA BE.

Co się dzieje na poziomie byte code?

Dlaczego warto patrzeć w byte code?


    public static void main(String[] args) {
        String result = "";
        for (int i = 0; i < 1_000_000; ++i) {
            result += i;
        }

        System.out.println(result);
    }

Dlaczego warto patrzeć w byte code?

    public static void main(String[] args) {
        String json = "{";
        json += "a:'someValue'";
    }
3: new           #3   // class java/lang/StringBuilder
6: dup
7: invokespecial #4   // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc           #6  // String a:'someValue'
16: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

An implementation (of compiler) may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.

0: ldc           #2     // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: ldc           #3    // int 1000000
8: if_icmpge     36
11: new           #4   // class java/lang/StringBuilder
14: dup
15: invokespecial #5   // Method java/lang/StringBuilder."<init>":()V
18: aload_1
19: invokevirtual #6   // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_2
23: invokevirtual #7   // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #8   // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_1
30: iinc          2, 1
33: goto          5
36: getstatic     #9   // Field java/lang/System.out:Ljava/io/PrintStream;
39: aload_1
40: invokevirtual #10  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
43: return

    public static void main(String[] args) {
        String result = "";
        for (int i = 0; i < 1_000_000; ++i) {
            result += i;
        }

        System.out.println(result);
    }

Klasa anonimowa

 public Main();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1    // Method java/lang/Object."<init>":()V
         4: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LMain;

  public static void main(java.lang.String...);
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=4, locals=2, args_size=1
         0: iconst_3    
         1: anewarray     #2        // class java/lang/String
         4: dup
         5: iconst_0
         6: ldc           #3        // String Mateusz
         8: aastore
            ...
        19: invokestatic  #6        // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
        22: astore_1
        23: aload_1
        24: new           #7        // class Main$1
        27: dup
        28: invokespecial #8        // Method Main$1."<init>":()V
        31: invokeinterface #9,  2  // InterfaceMethod java/util/List.sort:(Ljava/util/Comparator;)V
        36: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      37     0  args   [Ljava/lang/String;
           23      14     1 names   Ljava/util/List;
List<String> names = Arrays.asList("Mateusz", "Zosia", "Ania");

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

Klasa anonimowa

  Main$1();
    // analogiczny pusty konstruktor jak wcześniej

  public int compare(java.lang.String, java.lang.String);
    descriptor: (Ljava/lang/String;Ljava/lang/String;)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: aload_2
         2: invokevirtual #2    // Method java/lang/String.compareTo:(Ljava/lang/String;)I
         5: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   LMain$1;
            0       6     1    s1   Ljava/lang/String;
            0       6     2    s2   Ljava/lang/String;

  public int compare(java.lang.Object, java.lang.Object);
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=3, locals=3, args_size=3
         0: aload_0
         1: aload_1
         2: checkcast     #3    // class java/lang/String (checks objectref is of a certain type)
         5: aload_2
         6: checkcast     #3    // class java/lang/String
         9: invokevirtual #4    // Method compare:(Ljava/lang/String;Ljava/lang/String;)I
        12: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  this   LMain$1;

Instrukcje byte code do wywoływania metod

 

 
invokevirtual
invokeinterface
invokespecial
invokestatic
 public static void main(java.lang.String...);
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=4, locals=2, args_size=1
        // tworzenie stack frame, tablicy etc.
        19: invokestatic  #6        // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
        22: astore_1
        23: aload_1
        24: invokedynamic #7,  0    // InvokeDynamic #0:compare:()Ljava/util/Comparator;
        29: invokeinterface #8,  2  // InterfaceMethod java/util/List.sort:(Ljava/util/Comparator;)V
        34: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      35     0  args   [Ljava/lang/String;
           23      12     1 names   Ljava/util/List;

Lambda

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
// 1
void printElements(List<String> in) { 
    in.forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });
}

// 2
void printElements(List<String> in) {
  in.forEach(it -> System.out.println(it));
}

  // access flags 0x0
  // signature (Ljava/util/List<Ljava/lang/String;>;)V
  // declaration: void printElements(java.util.List<java.lang.String>)
  printElements(Ljava/util/List;)V
   L0
    LINENUMBER 6 L0
    ALOAD 1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    DUP
    INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
    POP
    INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
      // handle kind 0x6 : 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;
      // arguments:
      (Ljava/lang/Object;)V, 
      // handle kind 0x5 : INVOKEVIRTUAL
      java/io/PrintStream.println(Ljava/lang/String;)V, 
      (Ljava/lang/String;)V
    ]
    INVOKEINTERFACE java/util/List.forEach (Ljava/util/function/Consumer;)V
   L1
    LINENUMBER 7 L1
    RETURN
   L2
    LOCALVARIABLE this LGeneric; L0 L2 0
    LOCALVARIABLE strings Ljava/util/List; L0 L2 1
    // signature Ljava/util/List<Ljava/lang/String;>;
    // declaration: java.util.List<java.lang.String>
    MAXSTACK = 3
    MAXLOCALS = 2

Jaki byte code wygeneruje kompilator dla wyrażenia lambda?

InnerClasses:
     public static final #68= #67 of #71; 
            //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
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
// CallSite - holder for a variable
private static CallSite bootstrapLambda(Lookup lookup, String name, MethodType type){ //
  // lookup = provided by VM - factory for creating method handles
  // name = "lambda$printElements$0", 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);
}

pseudo kod!

  private static synthetic lambda$printElements$0(Ljava/lang/String;)V
   L0
    LINENUMBER 7 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
   L1
    LOCALVARIABLE it Ljava/lang/String; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

Metoda zawierająca ciało lambdy

// generated by Java compiler
private static void lambda$printElements$0(String item){ 
  System.out.println(item);
}

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

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

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

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

Functional interface

A functional interface is an interface that has just one abstract method (aside from the methods of Object), and thus represents a single function contract.

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

Functional interface

@FunctionalInterface
interface SomeInterface {
    void foo();

    default void bu() {}

    static void staticBu() {}
}
interface A {
    void foo();
        
    void bu();
}

@FunctionalInterface
interface B extends A {
    default void bu(){}
}
    
interface X { Iterable foo(Iterable<String> arg); }
interface Y { Iterable foo(Iterable arg); }
interface Z extends X, Y {}

Functional interface

@FunctionalInterface
interface MyFI {
    // ERROR: Multiple non-overriding abstract methods found in interface
    void method1();
    void method2();
}
@FunctionalInterface
interface NonFunc {
    boolean equals(Object obj);
}
interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}
@FunctionalInterface
interface NonFunc { }
interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}

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 = () -> {}; 
    }

http://mail.openjdk.java.net/pipermail/lambda-dev/2013-March/008441.html

  •  only 3% of the lambda candidate inner class instances had abstract classes as their target
  • not just user simplicity that is important here - VM simplicity is too
  • The payoff for a simpler model comes in many forms, including better performance for all lambda-using code.

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

 

 

Jak to działa?

    private static String identity(String param) {
        return param;
    }

    public static void main(String... args) {
        Function<String, String> fun = Main::identity;
        System.out.println(fun.apply("Hello"));
    }
stack=3, locals=2, args_size=1
    0: invokedynamic #2,  0     // InvokeDynamic #0:apply:()Ljava/util/function/Function;
    5: astore_1
    6: getstatic     #3         // Field java/lang/System.out:Ljava/io/PrintStream;
    9: aload_1
    10: ldc           #4         // String Hello
    12: invokeinterface #5,  2   // InterfaceMethod java/util/function/Function.apply:...
    17: checkcast     #6         // class java/lang/String
    20: invokevirtual #7         // Method java/io/PrintStream.println:(Ljava/lang/String;)V

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 + "-";
    }

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

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

Co nam dają

wyrażenia lambda

  • It reduces the line of code
  • Simplify functional programming
  • Easier to access the enclosing object - plain this reference refers to the instance of the enclosing class (and you don't need to say EnclosingClass.this)
  • There are no shadowing issues (as you cannot define local variables with the same names as variables in the enclosing scope)
  • Easier Sequential and Parallel execution
  • Passing behavior in methods
  • New form of old design patterns

Stream API

jako inversion of control

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.get(i));
}
  FluentIterable.from(holdings)
            .transformAndConcat(new com.google.common.base.Function<Holding, Iterable<Company>>() {
        @Override
        public Iterable<Company> apply(Holding input) {
            return input.getCompanies();
        }
    })
   .transformAndConcat(new com.google.common.base.Function<Company, Iterable<User>>() {
        @Override
        public Iterable<User> apply(Company input) {
            return input.getUsers();
        }
    })
   .filter(new com.google.common.base.Predicate<User>() {
        @Override
        public boolean apply(User input) {
            return input.getAge() > 18;
        }
    })
   .transform(new com.google.common.base.Function<User, String>() {
        @Override
        public String apply(User input) {
            return input.getFirstName() + " " + input.getLastName();
        }
    })
   .toSortedList(new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    }); // 31 linijka !

Guava bez lambdy

    FluentIterable.from(holdings)
                .transformAndConcat(new com.google.common.base.Function<Holding, Iterable<Company>>() {
                    @Override
                    public Iterable<Company> apply(Holding input) {
                        return input.getCompanies();
                    }
                })
                .transformAndConcat(new com.google.common.base.Function<Company, Iterable<User>>() {
                    @Override
                    public Iterable<User> apply(Company input) {
                        return input.getUsers();
                    }
                })
                .filter(new com.google.common.base.Predicate<User>() {
                    @Override
                    public boolean apply(User input) {
                        return input.getAge() > 18;
                    }
                })
                .transform(new com.google.common.base.Function<User, String>() {
                    @Override
                    public String apply(User input) {
                        return input.getFirstName() + " " + input.getLastName();
                    }
                })
                .toSortedList(new Comparator<String>() {
                    @Override
                    public int compare(String o1, String o2) {
                        return o1.compareTo(o2);
                    }
                }); // 31 linijka !

Guava bez lambdy

          holdings
                .stream()
                .flatMap(holding -> holding.getCompanies().stream())
                .flatMap(company -> company.getUsers().stream())
                .filter(user -> user.getAge() > 18)
                .map(input -> input.getFirstName() + " " + input.getLastName())
                .sorted()
                .collect(toList())

Stream API

Czytelność strumieni

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}

Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});

List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}
List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

source: http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html

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

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

Kiedy nie używać strumieni?

Kiedy nie używać strumieni?

  • kiedy strumień wprowadza zbędną złożoność w kodzie a jego użycie tylko komplikuje całość
  • 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);
            });
    });

Jak nie używać lambdy?

return apiChain -> apiChain
 .prefix("games",
         chain -> chain
                 .prefix("game", games ->
                         games.post(":id", ctx -> {
                             final String gameUUID = ctx.getPathTokens().get("id");
                             renderSecure(ctx, sess -> getSessionCompletionStageFunction(sess, gameUUID));
                         })
                 )
                 .prefix("games", games ->
                         games.all(noGameId ->
                                 noGameId.byMethod(m -> m.get(listGames(noGameId)).post(createGame(noGameId))))
                 )
                 .prefix("players", moves ->
                         moves.post(":id", ctx -> {
                             final String gameId = ctx.getPathTokens().get("id");
                             ctx.getRequest().getBody()
                                     .then(body -> {
                                         final float targetY = Float.parseFloat(body.getText());
                                         renderSecure(ctx, session ->
                                                 gamesRepo.movePaddle(gameId, session.userId, targetY));
                                     });
                         }))
                 .prefix("stream", stream ->
                         stream.get(":id", ctx -> {
                             final String gameId = ctx.getPathTokens().get("id");
                             final Option<Flowable<GameState>> gsOpt = Option.of(this.gamesFlow.get(gameId));
                             gsOpt.forEach(gsFlow -> WebSockets
                                     .websocketBroadcast(ctx, gsFlow
                                             .map(val -> chain.getRegistry()
                                                     .get(ObjectMapper.class)
                                                     .writeValueAsString(val))));
                         })));

based on: https://github.com/javaFunAgain/ratpong

Closures

Co to jest?

Domknięcia to funkcje których funkcje wewnętrzne odwołują się do niezależnych (wolnych) zmiennych. Innymi słowy, funkcje zdeklarowane wewnątrz domknięcia 'pamiętają' środowisko w którym zostały utworzone.

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}
const makeAdder = x => y => x + y;
const add5 = makeAdder(5);
console.log(add5(2));  // 7
public class Main {
    private static Function<Integer, Integer> makeAdder(final Integer x) {
        return y -> x + y;
    }

    public static void main(String[] args) {
        final Function<Integer, Integer> adder = makeAdder(10);
        int result = adder.apply(5);
        System.out.println(result);
    }
}
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Jak to zrobić w Javie?

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

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

Wydajność

Jaki wpływ na wydajność ma bootstraping lambdy?

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

Imagine a programming language without variables. A language without function parameters and return values. A language without floating point numbers, maybe even without multiplication and division. A language without type casting. Without types, actually.

Paulo Asemblelho

public class Main {
    final static IntConsumer r = (i) -> System.out.print("");

    public static void main(String[] args) throws IOException {

        Main main = new Main();
        for (int i = 0; i < 90_0000; ++i) {
            main.runRunnable();
        }
    }

    public void runRunnable() {
        IntStream.range(0, 2).forEach(Main.r);
    }
}

-XX:CompileCommand=print,*Main.runRunnable

  -XX:+UnlockDiagnosticVMOptions

Skąd ta liczba?

Co wygeneruje kompilacja JIT?

@ 0   Main::lambda$static$0 (9 bytes)   inline (hot)

Compiled method (c2)     195  235       4       Generic::lambda$static$0 (9 bytes)

-XX:+PrintInlining

Inne optymalizacje

ompilerOracle: print Main.*
Compiled method (c1)     211  221       3       Main::lambda$static$0 (9 bytes)
 total in heap  [0x00007fcfa115f450,0x00007fcfa115f808] = 952
 relocation     [0x00007fcfa115f578,0x00007fcfa115f5b8] = 64
 main code      [0x00007fcfa115f5c0,0x00007fcfa115f6a0] = 224
 stub code      [0x00007fcfa115f6a0,0x00007fcfa115f748] = 168
 oops           [0x00007fcfa115f748,0x00007fcfa115f750] = 8
 metadata       [0x00007fcfa115f750,0x00007fcfa115f760] = 16
 scopes data    [0x00007fcfa115f760,0x00007fcfa115f780] = 32
 scopes pcs     [0x00007fcfa115f780,0x00007fcfa115f7f0] = 112
 dependencies   [0x00007fcfa115f7f0,0x00007fcfa115f7f8] = 8
 nul chk table  [0x00007fcfa115f7f8,0x00007fcfa115f808] = 16
Loaded disassembler from /usr/java/jdk1.8.0_151/jre/lib/amd64/hsdis-amd64.so
Decoding compiled method 0x00007fcfa115f450:
Code:
[Disassembling for mach='i386:x86-64']
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x00007fcf9b42ee18} 'lambda$static$0' '(I)V' in 'Main'
  # parm0:    rsi       = int
  #           [sp+0x40]  (sp of caller)
  : mov    %eax,-0x14000(%rsp)
  : push   %rbp
  : sub    $0x30,%rsp
  : movabs $0x7fcf9b42f250,%rdx  
                ;{metadata(method data for {method} {0x00007fcf9b42ee18} 'lambda$static$0' '(I)V' in 'Main')}
  : mov    0xdc(%rdx),%edi
  : add    $0x8,%edi
  : mov    %edi,0xdc(%rdx)
  : movabs $0x7fcf9b42ee18,%rdx  
                ;{metadata({method} {0x00007fcf9b42ee18} 'lambda$static$0' '(I)V' in 'Main')}
  : and    $0x1ff8,%edi
  : cmp    $0x0,%edi
  : je     0x00007fcfa115f648
  : movabs $0x76c800c78,%rdx  ;   {oop(a 'java/lang/Class' = 'java/lang/System')}

Brak mechanizmu generowania lambdy

#0 
OopMap{off=124}
#1 
OopMap{off=154}
#2 
OopMap{rsi=Oop off=161}
Compiled method (c1)     215  226       3       Main::runRunnable (14 bytes)
 total in heap  [0x00007fcfa11639d0,0x00007fcfa1163ea0] = 1232
 relocation     [0x00007fcfa1163af8,0x00007fcfa1163b48] = 80
 main code      [0x00007fcfa1163b60,0x00007fcfa1163d00] = 416
 stub code      [0x00007fcfa1163d00,0x00007fcfa1163db8] = 184
 oops           [0x00007fcfa1163db8,0x00007fcfa1163dc0] = 8
 metadata       [0x00007fcfa1163dc0,0x00007fcfa1163dc8] = 8
 scopes data    [0x00007fcfa1163dc8,0x00007fcfa1163df8] = 48
 scopes pcs     [0x00007fcfa1163df8,0x00007fcfa1163e88] = 144
 dependencies   [0x00007fcfa1163e88,0x00007fcfa1163e90] = 8
 nul chk table  [0x00007fcfa1163e90,0x00007fcfa1163ea0] = 16
Decoding compiled method 0x00007fcfa11639d0:
Code:
....
  0x00007fcfa1163c6a: jmpq   0x00007fcfa1163c77
  0x00007fcfa1163c6f: addq   $0x1,0x118(%rsi)
  0x00007fcfa1163c77: movabs $0x76cd84c18,%rdx  ;   {oop(a 'Main$$Lambda$1')}
  0x00007fcfa1163c81: mov    %rax,%rsi          ;*invokeinterface forEach
                                                ; - Main::runRunnable@8 (line 17)
.... 

 0x00007f65ad16c61a: mov    %rax,%rsi
  0x00007f65ad16c61d: jmp    0x00007f65ad16c622  ;*invokestatic lambda$static$0
                                                ; - Main$$Lambda$1/1831932724::accept@1
                                                ; - java.util.stream.Streams$RangeIntSpliterator::forEachRemaining@44 (line 110)
                                                ; - java.util.stream.IntPipeline$Head::forEach@15 (line 557)
                                                ; - Main::runRunnable@8 (line 17)
                                                ; - Main::main@17 (line 12)

Skąd się wzieło Main$$Lambda$1 ?

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);
}
void printElements(List<String> in) {
  in.forEach(it -> System.out.println(it));
}

void printElements(List<String> strings){
  Consumer<String> lambda = invokedynamic(#bootstrapLambda)
  strings.forEach(lambda);
}
 public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }
lambdaClassName = targetClass
                            .getName()
                            .replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();

Skąd się wzieło Main$$Lambda$1 ?

public class Main {
    public static void main(String[] args) {
        Function<String, String> identity = str -> str;

        // class Main$$Lambda$1/81628611
        System.out.println(identity.getClass());

        // interface java.util.function.Function
        Arrays.asList(identity.getClass().getInterfaces()).forEach(System.out::println);
    }
}

Skąd się wzieło Main$$Lambda$1 ?

Method Handles

Java 7

The Java 7 release introduced a new very important feature into the JVM and Java standard library – method handles. Method handle is a typed, directly executable reference to an underlying method, constructor or field (or similar low-level operation) with optional transformations of arguments or return values. They are in many respects better alternative to method invocations performed using the Reflection API.

Method handles

public class Main {
    public static void printNumber(final int number) {
        System.out.println("My number is:" + number);
    }

    public static void main(String[] args) throws Throwable {
        final Method printHello = Main.class.getMethod("printNumber", int.class);
        printHello.invoke(Main.class, 1);
    }
}

Old school reflection

public class Main {
    public static void printNumber(final int number) {
        System.out.println("My number is:" + number);
    }

    public static void main(String[] args) throws Throwable {
        final MethodType methodType = MethodType.methodType(void.class, int.class);
        final MethodHandle methodHandle = MethodHandles
                                                    .lookup()
                                                    .findStatic(Main.class, "printNumber", methodType);

        System.out.println(methodHandle.invoke(1));
    }
}
reflective invocation (without setAccessible)   568.506 ns
reflective invocation (with setAccessible)  42.377 ns
methodhandle invocation 27.461 ns
static final methodhandle invocation    9.402 ns
direct invocation   9.363 ns

MethodHandle performance

https://stackoverflow.com/questions/14146570/calling-a-getter-in-java-though-reflection-whats-the-fastest-way-to-repeatedly/14146919#14146919

public class Main {
    public static String hello() {
        return "Hello";
    }

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(Object.class);
        MethodType actualMethodType = MethodType.methodType(String.class);
        MethodType invokedType = MethodType.methodType(Supplier.class);

        CallSite site = LambdaMetafactory.metafactory(caller,
                "get",
                invokedType,
                methodType,
                caller.findStatic(Main.class, "hello", actualMethodType),
                methodType);
        MethodHandle factory = site.getTarget();

        Supplier<String> r = (Supplier<String>) factory.invoke();
        System.out.println(r.get());
    }
}

Użycie LambdaMetafactory

Czy wczytywanie osobnego pliku

wpływa na wydajność?

source: https://video.oracle.com/detail/videos/featured-videos/video/2623576348001

hot jvm

cold jvm

public static Level get1024 ( final String p ){
    return new Level () {
            @Override
            public Level up () {
                return get1023 ( p );
        }
    };
}
public static Level get1024 ( String p ) {
    return () -> get1023 ( p );
}

// () -> () -> () -> () -> () -> ... -> null

how drastically a small problem size and only one iteration can affect the results. The results in this table show how using a problem size of 1000, only running each experiment 100

Performance of Lambda Expressions in Java 8, A. Ward 1 , and D. Deugo 1
School of Computer Science, Carleton University, Ottawa, Ontario, Canada

expression for problems of size 10,000, repeating each experiment 1000 times and then averaging the results

Stream with lambda performance

Example: List<String> upperNames = names
    .stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());
ArrayList<String> upperNames = new
    ArrayList<String>();
    for (String name : names) {
        upperNames.add(name.toUpperCase());
}

Problemy

wyrażeń lambda

Dziury w systemie typów

Function<Integer, Integer> fun1 = x -> x + 2;
Function<Integer, Integer> fun2 = x -> x * x;

Stream.of(fun1, fun2).forEach(f -> f.apply(5));    
Function<Integer, Integer> fun1 = x -> x + 2;

Stream.of(fun1, x -> x * x).forEach(f -> f.apply(5));
Stream.of(x -> x + 2, x -> x * x).forEach(f -> f.apply(5));

Dziury w systemie typów

public class Main {
    private static String someText = "unicorn";

    private static boolean foo(Predicate<String> param) {
        return param.test(someText);
    }

    private static String foo(Function<String, String> param) {
        return param.apply(someText);
    }

    public static void main(String... args) {
        final Predicate<String> stringPredicate = str -> str.length() == 0;
        foo(stringPredicate);

        final Function<String, String> identity = str -> str;
        foo(identity);
    }
}
    public static void main(String... args) {
        foo(str -> str.length() == 0);
        foo(str -> str);
    }

Dziury w systemie typów

public class Main {
    public final void bar(InterfaceA a) { }

    public final void bar(InterfaceB b) { }

    public interface InterfaceA {
        Double doSomething(Double a);
    }

    public interface InterfaceB {
        Integer doSomething(Integer a);
    }

    public static void main(String[] args) {
        Main foo = new Main();

        foo.bar(a -> 1);
        foo.bar((Integer a) -> 1);

        foo.bar(a -> 1.0);
        foo.bar((Double a) -> 1.0);
    }
}

Dziury w systemie typów

public static void main(String[] args) {
    ObservableMap<String, String> map = FXCollections.observableMap(new HashMap<>());
    
    // 1. OK
    map.addListener(
        (MapChangeListener.Change<? extends String, ? extends String> change) -> {}
    );

    // 2. OK
    map.addListener(
        (MapChangeListener<String, String>) change -> {}
    );

    // 3. Ambiguous method call error
    map.addListener(change -> {});
}
public interface ObservableMap<K, V> extends Map<K, V>, Observable {
    /**
     * Add a listener to this observable map.
     * @param listener the listener for listening to the list changes
     */
    public void addListener(MapChangeListener<? super K, ? super V> listener);
}

JEP 302: Lambda Leftovers

now candidate, Java 11?

JEP 302 - Treatment of underscores

 Phase 1 was forbidding underscore as a lambda formal parameter name in Java 8 (this had no compatibility consequence, since lambdas did not exist previously) and a warning was issued for using underscore as an identifier in other places. Phase 2 came in Java 9, when this warning became an error.

 

We are now free to complete the planned rehabilitation of underscore to indicate an unused lambda, method, or catch formal parameter.

BiFunction<Integer, String, String> biss = (i, _) -> String.valueOf(i);

JEP 302 - Shadowing of lambda parameters

 Lambda parameters are not allowed to shadow variables in the enclosing scopes. It would be desirable to lift this restriction, and allow lambda parameters (and locals declared with a lambda) to shadow variables defined in enclosing scopes. (One possible argument against is readability.)

Map<String, Integer> msi = ...
String key = computeSomeKey();

msi.computeIfAbsent(key, key -> key.length()) 

Wymazywanie typów

interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {} // OK
// Error:(10, 5) java: name clash: m(java.lang.Iterable<java.lang.Integer>) in 
// Main.Y and m(java.lang.Iterable<java.lang.String>) in Main.X have the same erasure, 
// yet neither overrides the other
interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}

Method reference type

@FunctionalInterface
interface MagicFunction extends Function<String, String> { }
// 2. java.lang.ClassCastException
Function<String, String> trim = String::trim;
final MagicFunction fun2 = (MagicFunction) trim;

System.out.println((String::trim).getClass()); // error
// 1. OK
final MagicFunction fun1 = String::trim;

Adapter method

- Joshua Bloch

public class Main {
   private static Stream<String> getItems() {
        return Stream.<String>builder()
                .add("John")
                .build();
    }

    public static void main(String[] args) throws Throwable {
         for (String item : (Iterable<String>) getItems()::iterator) {
            // process items
        }
    }
}
public class Main {
    private static Stream<String> getItems() {
        return Stream.<String>builder()
                .add("John")
                .build();
    }

    public static <E> Iterable<E> streamOf(Stream<E> stream) {
        return stream::iterator;
    }

    public static void main(String[] args) throws Throwable {
        for (String item : streamOf(getItems())) {
            // process items
        }
    }
}

Nie każdy @FunctionalInterface można zapisać jako lambda

source: Java language specification 9.9-2

interface G1 {
    <E extends Exception> Object m() throws E;
}
interface G2 {
    <F extends Exception> String m() throws Exception;
}

interface G extends G1, G2 {}

// ...
final G g1 = Main::foo;  // OK
final G g2 = () -> null; // ERROR

The function type of G is: <F extends Exception> ()->String throws F
A generic function type for a functional interface may be implemented by a method reference expression (§15.13), but not by a lambda expression (§15.27) as there is no syntax for generic lambda expressions.

JEP 286: Local-Variable Type Inference

var list = new ArrayList<String>(); // infers ArrayList<String>
var stream = list.stream();         // infers Stream<String>

Scanning the OpenJDK code base for local variable declarations, we found that 13% cannot be written using var, since there is no initializer, the initializer has the null type, or (rarely) the initializer requires a target type. Among the remaining local variable declarations:

  • 94% have an initializer with the exact type present in the source code (63% of cases with parameterized types)
  • 5% have an initializer with some sharper denotable type (29% of cases with parameterized types)
  • 1% have an initializer with a type that mentions a capture variable (7% of cases with parameterized types)
  • <1% have an initializer with an anonymous class type or intersection type (same for cases with parameterized types)

JEP 286: Co z lambdą?

Main.java:82: error: cannot infer type for local
variable f
        var f = () -> { };
            ^
  (lambda expression needs an explicit target-type) 

The initializer has no target type (because we haven't inferred it yet). Poly expressions that require such a type, like lambdas, method references, and array initializers, will trigger an error.

Conflicts with a compiler-synthesized symbol

public class Main {
    static Object lambda$main$0(Object o) {
        return null;
    }

    public static void main(String[] args) throws Throwable {
        Function f = str -> str;
        lambda$main$0(null);
    }
}

/home/lolcio/programowanie/empty-java/src/main/java/Main.java
Error:(5, 19) java: the symbol lambda$main$0(java.lang.Object) conflicts with a compiler-synthesized symbol in Main
Error:(1, 1) java: the symbol lambda$main$0(java.lang.Object) conflicts with a compiler-synthesized symbol in Main

Serializacja

Po co serializować lambdę?

  • w celu uruchomienia kodu na zdalnym serwerze np. kiedy implementujemy wzorzec wizytor

Serializacja

You can serialize a lambda expression if its target type and its captured arguments are serializable. However, like inner classes, the serialization of lambda expressions is strongly discouraged.

https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html#serialization

Ale dlaczego jest niezalecana?

https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html#serialization

When the Java compiler compiles certain constructs, such as inner classes, it creates synthetic constructs; these are classes, methods, fields, and other constructs that do not have a corresponding construct in the source code. Synthetic constructs enable Java compilers to implement new Java language features without changes to the JVM. However, synthetic constructs can vary among different Java compiler implementations, which means that .class files can vary among different implementations as well. Consequently, you may have compatibility issues if you serialize an inner class and then deserialize it with a different JRE implementation.

public class Syntetic {
    private class InnerClass {
        private String var = "This is a private variable in InnerClass";
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Syntetic.InnerClass innerClass = new Syntetic().new InnerClass();
        System.out.println(innerClass.var);
    }
}
~ javap ./Syntetic\$InnerClass.class 
Compiled from "Syntetic.java"
class Syntetic$InnerClass {
  final Syntetic this$0;
  Syntetic$InnerClass(Syntetic, Syntetic$1);
  static java.lang.String access$100(Syntetic$InnerClass);
}
 static java.lang.String access$100(Syntetic$InnerClass);
    descriptor: (LSyntetic$InnerClass;)Ljava/lang/String;
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #1                  // Field var:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0    x0   LSyntetic$InnerClass;
}
public class Syntetic {
    private class InnerClass {
        private String var = "This is a private variable in InnerClass";
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Syntetic.InnerClass innerClass = new Syntetic().new InnerClass();
        System.out.println(innerClass.var);
        System.out.println(
                    innerClass.getClass().getDeclaredMethod("access$100", InnerClass.class));
    }
}
This is a private variable in InnerClass
static java.lang.String Syntetic$InnerClass.access$100(Syntetic$InnerClass)

Serializacja lamdy - przykład

  • Hazelcast dostarcza implementacji ExecutorService która pozwala na w wykonanie kodu asynchronicznie w klastrze
  • należy dysponować jednak serializowamym obiektem.

* Hazelcast  is an open source in-memory data grid based on Java. It simplify: application scaling, build cache as service, shared storage, in memory computing, memcache, web session clustering etc

HazelcastInstance instance = 
                Hazelcast.newHazelcastInstance(new Config());

ExecutorService executorService = 
                instance.getExecutorService("massExecutionWithLambdas");
 
executorService.submit( () -> System.out.println("Running function") );

/**                          |
                            \|/
                             '
com.hazelcast.nio.serialization.HazelcastSerializationException: 
    There is no suitable serializer for class .....$$Lambda$2/.....
*/

Serializacja lamdy - przykład

executorService.submit( (Runnable & Serializable)  () -> {       
    System.out.println("Running function");
} );

Rozwiązanie: Zrzutowanie do dwóch interfejsów jednoczesnie

executorService.submit( () -> {      
    System.out.println("Running function");
});

Co jest w byte code serializowanej lambdy?

// access flags 0x100A
  private static synthetic $deserializeLambda$(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;
....
    ICONST_0
    INVOKEVIRTUAL java/lang/invoke/SerializedLambda.getCapturedArg (I)Ljava/lang/Object;
    CHECKCAST java/io/PrintStream
    INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.altMetafactory(Ljava/lang/invoke/MethodHandles$Lookup;
    Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      // arguments:
      (Ljava/lang/Object;)V, 
      // handle kind 0x5 : INVOKEVIRTUAL
      java/io/PrintStream.println(Ljava/lang/Object;)V, 
      (Ljava/lang/Object;)V, 
      5, 
      0
    ]
    ARETURN
   L4
   FRAME CHOP 2
    NEW java/lang/IllegalArgumentException
    DUP
    LDC "Invalid lambda deserialization"
    INVOKESPECIAL java/lang/IllegalArgumentException.<init> (Ljava/lang/String;)V
    ATHROW
   L5
    LOCALVARIABLE lambda Ljava/lang/invoke/SerializedLambda; L0 L5 0
    MAXSTACK = 3
    MAXLOCALS = 3
}

Co jest w byte code serializowanej lambdy?

SerializedLambda has a readResolve method that looks for a (possibly private) static method called $deserializeLambda$(SerializedLambda) in the capturing class, invokes that with itself as the first argument, and returns the result.

 

Lambda classes implementing $deserializeLambda$ are responsible for validating that the properties of the SerializedLambda are consistent with a lambda actually captured by that class.

Błąd w jvm?

public class Generic {

    interface Foo {
        default void greet() {
            System.out.println(this); // Generic$$Lambda$1/931919113
        }
    }

    public static void main(String[] args) {
        Foo f = (Consumer & Foo) System.out::println;
        f.greet();
    }

}

Debugowanie

STRUMIENI

Czy naprawdę jest to problem?

 

Exception in thread "main" java.lang.NullPointerException
  at Test$$Lambda$3/455659002.apply(Unknown Source)
  at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
  at java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
  at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
  at java.util.Spliterators$ArraySpliterator.forEachRemaining(Unknown Source)
  at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
  at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
  at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
  at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
  at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
  at java.util.stream.ReferencePipeline.forEach(Unknown Source)
  at Test.main(Test.java:12)
    final List<String> list = Arrays.asList("foo", null, "bar");
    list.stream()
        .map(Function.identity())
        .filter(x -> true)
        .map(String::length)
        .forEach(System.out::println);

Czy naprawdę jest to problem?

 

  • Co z programowaniem wielowątkowym i debugowaniem?
  • Testy jednostkowe = mniej błędów
  • Unikanie logiki w wyrażeniach lambda = metody w których można ustawić break point

O tym jak sobie pomóc

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

Java stream debugger

Intellij plugin

Evaluate code

Koniec

 

Czas na pytania

https://slides.com/kamillolo/secrets-of-lambda

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/

Secrets of lambda

By Kamil Lolo

Secrets of lambda

Prelekcja tłumaczy czym są wyrażenia lambda jaka jest róznica miedzy nimi a klasą anonimową. Zarówno na od strony programistycznej jak i od strony byte code. Wyjaśnie czym jest programowanie funkcyjne oraz w jaki sposób wyrażenia lambda ułatwiają pisanie takiego kodu. Zbliżając nas choć trochę do języków funkcyjnych. Nie sposób nie wspomnieć o strumieniach, które razem z lambdą stanowią potężna broń w programistycznym arsenale. Nie jest jednak tak idealnie z lambdą w Javie, Powiem o tym na jakie problemy możecie natrafić używając tego mechanizmu i co twórcy języka mogli zrobić lepiej. Na sam koniec wspomnę nawet o tym w jaki sposób debugować strumienie, jak ułatwić sobie życie. Prelekcja zarówno dla osób które dopiero przesiadają się na nowszą Jave oraz dla tych którzy używają, a nie znajo tak naprawdę.

  • 1,435