$30 off During Our Annual Pro Sale. View Details »

Java 8 Lambda Expressions

Takipi
April 30, 2014

Java 8 Lambda Expressions

Java 8 introduced a change named Java's biggest ever -- lambda expressions. In this talk we'll explore lambda expressions, when and how to use them, then dive even deeper and see how they work under the hood.

Takipi

April 30, 2014
Tweet

More Decks by Takipi

Other Decks in Programming

Transcript

  1. Java 8 λs IL JUG, April 2014 Niv Steingarten, Takipi

  2. Outline • What are lambda expressions? • Java 8 lambdas

    + Stream API • Syntax • Under the hood • Questions
  3. Lambda expressions

  4. Lambda expressions Anonymous implementation of a functional interface

  5. Lambda expressions List<String> list = Arrays.asList( "Say", "hello", "to", "Java

    8", "Lambdas"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String x, String y) { return x.compareTo(y); } }); System.out.println(list);
  6. Lambda expressions List<String> list = Arrays.asList( "Say", "hello", "to", "Java

    8", "Lambdas"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } }); System.out.println(list);
  7. Lambda expressions Java < 8 Collections.sort(list, new Comparator<String>() { @Override

    public int compare(String x, String y) { return x.compareTo(y); } });
  8. Lambda expressions Java < 8 Collections.sort(list, new Comparator<String>() { @Override

    public int compare(String x, String y) { return x.compareTo(y); } }); Java 8 list.sort((x, y) -> x.compareTo(y));
  9. Lambda expressions List<Integer> list = Arrays.asList(3, 9, 12, -1); int

    factor = getFactor(); list.forEach(x -> System.out.println(x * factor));
  10. When can we use λs?

  11. When can we use λs? “SAM” Single Abstract Method

  12. When can we use λs? @FunctionalInterface public interface Comparator<T> {

    int compare(T o1, T o2); }
  13. When can we use λs? @FunctionalInterface public interface Comparator<T> {

    int compare(T o1, T o2); } @FunctionalInterface public interface Predicate<T> { boolean test(T t); }
  14. When can we use λs? Java < 8 new Thread(new

    Runnable() { @Override public void run() { System.out.println("Lambdas can have"); System.out.println("multiple statements"); } }).start();
  15. When can we use λs? Java 8 new Thread(() ->

    { System.out.println("Lambdas can have"); System.out.println("multiple statements"); }).start();
  16. Streams + Lambdas

  17. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); List<Double>

    newPrices = new ArrayList<Double>(); for (Double p : prices) { System.out.println(p); if (p > 300) { newPrices.add(p * (1.0 - dailyDiscount)); } } Collections.sort(newPrices); for (Double p : newPrices) { System.out.println(p); }
  18. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(p -> p * (1.0 - dailyDiscount)) .sorted() .forEach(p -> System.out.println(p));
  19. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(p -> p * (1.0 - dailyDiscount)) .sorted() .forEach(p -> System.out.println(p)); Consumer: T → void
  20. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(p -> p * (1.0 - dailyDiscount)) .sorted() .forEach(p -> System.out.println(p)); Predicate: T → boolean
  21. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(p -> p * (1.0 - dailyDiscount)) .sorted() .forEach(p -> System.out.println(p)); Function: T → R
  22. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(p -> p * (1.0 - dailyDiscount)) .sorted() .forEach(p -> System.out.println(p)); Requires: T implements Comparable
  23. Streams + Lambdas List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(p -> p * (1.0 - dailyDiscount)) .sorted() .forEach(p -> System.out.println(p)); Consumer: (+ terminal operation) T → void
  24. Syntax

  25. Syntax • (int x) -> { int y = x

    + 1; return y; }
  26. Syntax • (int x) -> { int y = x

    + 1; return y; } • (int x) -> { return x + 1; }
  27. Syntax • (int x) -> { int y = x

    + 1; return y; } • (int x) -> { return x + 1; } • (int x) -> x + 1
  28. Syntax • (int x) -> { int y = x

    + 1; return y; } • (int x) -> { return x + 1; } • (int x) -> x + 1 • (x) -> x + 1
  29. Syntax • (int x) -> { int y = x

    + 1; return y; } • (int x) -> { return x + 1; } • (int x) -> x + 1 • (x) -> x + 1 • x -> x + 1
  30. Syntax • (int x) -> { int y = x

    + 1; return y; } • (int x) -> { return x + 1; } • (int x) -> x + 1 • (x) -> x + 1 • x -> x + 1 • (x, y) -> x * y
  31. Off-topic: Method references List<Double> prices = Arrays.asList(1000.0, 150.0, 499.0); prices.stream()

    .peek(p -> System.out.println(p)) .filter(p -> (p > 300)) .map(Double::toString) .sorted() .forEach(p -> System.out.println(p));
  32. Under the hood

  33. Java bytecode

  34. Java bytecode ALOAD 0 GETFIELD java/util/ArrayList.elementData : [Ljava/lang/Object; GETSTATIC java/util/ArrayList.EMPTY_ELEMENTDATA

    : [Ljava/lang/Object; IF_ACMPEQ L1 ICONST_0 GOTO L2 BIPUSH 10 ISTORE 2 ILOAD 1 ILOAD 2 IF_ICMPLE L4 ALOAD 0 ILOAD 1 INVOKESPECIAL java/util/ArrayList.ensureExplicitCapacity (I)V RETURN
  35. Java bytecode • Designed for Java 1

  36. Java bytecode • Designed for Java 1 • Strict Object-Oriented

    specification
  37. Java bytecode • Designed for Java 1 • Strict Object-Oriented

    specification • Highly-performant on modern JVMs
  38. Under the hood List<String> list = Arrays.asList("Alice", "Bob", "Eve"); list.forEach(s

    -> System.out.println(s));
  39. Under the hood List<String> list = Arrays.asList("Alice", "Bob", "Eve"); list.forEach(s

    -> System.out.println(s));
  40. Under the hood ... aload_0 invokedynamic #0:accept:()LConsumer; invokeinterface List.forEach:(LConsumer;)V ...

  41. Under the hood Step 1: Desugaring

  42. Desugaring class A { public void foo() { List<String> list

    = ... list.forEach(s -> System.out.println(s)); } }
  43. Desugaring class A { public void foo() { List<String> list

    = ... list.forEach([await link of lambda$1 Consumer]); } private static void lambda$1(String s) { System.out.println(s); } }
  44. Desugaring class B { public void foo() { List<Integer> list

    = ... int factor = ... list.map(n -> n * factor); } }
  45. Desugaring class B { public void foo() { List<Integer> list

    = ... int factor = ... list.map([await link of lambda$1 as Function]); } private static Integer lambda$1(int factor, Integer n) { return n * factor; } }
  46. Under the hood Step 2: The Lambda Metafactory

  47. The Lambda Metafactory metaFactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodHandle

    descriptor, MethodHandle impl)
  48. The Lambda Metafactory

  49. The Lambda Metafactory • “invokedynamic” encountered

  50. The Lambda Metafactory • “invokedynamic” encountered • Metafactory is invoked

    once
  51. The Lambda Metafactory • “invokedynamic” encountered • Metafactory is invoked

    once • ...returns a specific “Lambda Factory”
  52. The Lambda Metafactory • “invokedynamic” encountered • Metafactory is invoked

    once • ...returns a specific “Lambda Factory” • Call site linked to the specific factory forever
  53. Under the hood Step 3: The Lambda Factory

  54. The Lambda Factory class A { public void foo() {

    List<String> list = ... list.forEach([lambda$1 factory]); } private static void lambda$1(String s) { System.out.println(s); } }
  55. The Lambda Factory class lambda$1Factory { public Consumer<String> create(...) {

    ... } }
  56. The Lambda Factory class A { public void foo() {

    List<String> list = ... list.forEach([lambda$1Factory]); } }
  57. Under the hood Step 4: The Lambda

  58. The Lambda class Lambda$1Impl implements Consumer<String> { @Override public void

    accept(String s) { A.lambda$1bridge(s); } }
  59. The Lambda class Lambda$1Impl implements Consumer<String> { @Override public void

    accept(String s) { A.lambda$1bridge(s); } }
  60. But why?

  61. But why? • Try not to touch the bytecode specification

  62. But why? • Try not to touch the bytecode specification

    • Delegates lambda handling to the JVM
  63. But why? • Try not to touch the bytecode specification

    • Delegates lambda handling to the JVM • While allowing the compiler minimal control
  64. But why? • Try not to touch the bytecode specification

    • Delegates lambda handling to the JVM • While allowing the compiler minimal control
  65. But why? • Try not to touch the bytecode specification

    • Delegates lambda handling to the JVM • While allowing the compiler minimal control • Enabled runtime optimization and analysis
  66. But why? • Try not to touch the bytecode specification

    • Delegates lambda handling to the JVM • While allowing the compiler minimal control • Enabled runtime optimization and analysis • Allow flexibility in internal implementation
  67. Thank you! Now go and be awesome. niv@takipi.com @nivstein /

    @takipid