This part tries to summarise general paradigm of Functional Programming in Java 8. Future parts cover other aspects of Java 8.

Functional Programming

  • Block of code with parameters
  • Block of code executed at a later point (i.e. callback)
  • Well suited for concurrent and event-driven (or “reactive”) programming
  • Method and constructor references refer to methods or constructors without invoking them
  • Add default and static methods to interfaces
    • Any conflicts between default methods from multiple interfaces must be resolved manually
  • Lambda expression
    • More efficient than using traditional inner classes
    • Access effectively final variables from the enclosing scope
    • Applicable on an object of an interface with a single abstract method! Such an interface is called a functional interface
      • Such interface can be tagged by @FunctionalInterface
      • The compiler asserts that the annotated entity is an interface with a single abstract method

General Syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
/* General Syntax (similar to other functional declaration in other languages,
   (v1, v2, ...) -> return_type) */
([[TYPE1] VAR1, [TYPE2] VAR2, ...]) -> TYPE

// So it can be 
([[TYPE1] VAR1, [TYPE2] VAR2, ...]) -> EXPRESSION | STATEMENT

// or
([[TYPE1] VAR1, [TYPE2] VAR2, ...]) -> {
  STATEMENT_1;
  STATEMENT_2;
  ...
}
  • A lambda can have no parameter (e.g. Runnable)
  • Parameter types of a lambda expression can be inferred
    • In this case, all parameters’ type are inferred and must declared without explicit type name!
  • Parentheses can be omitted for single & inferred-type-parameter lambda
    1
    2
    
    Button b = new Button();
    b.addActionListener(event -> System.out.println("button clicked"));
    
  • In spite of other languages, function types such as (String, String) -> int must be assigned to a specific type in Java! So a lambda expression must be assigned to a variable of functional interface type:
    1
    2
    3
    4
    5
    
    Comparator<String> comparator = 
      (str1, str2) -> Integer.compare(str1.length(), str2.length());
    
    ToIntBiFunction<String, String> handler =
      (str1, str2) -> Integer.compare(str1.length(), str2.length());
    
  • A lambda expression has three ingredients:
    • A block of code
    • Parameters
    • Values for the free variables: the variables that are not parameters and not defined inside the code.

      Let’s suppose an example for implementation detail: one can translate a lambda expression into an object with a single method, so that the values of the free variables are copied into instance final variables of that object.

      Note: The technical term for a block of code together with the values of the free variables is a closure.

      In fact, inner classes have been closures all along. Java 8 gives it an attractive syntax.

  • The body of a lambda expression has the same scope as a nested block.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import org.junit.Before;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

public class TestFunP {
  private static final List<String> EXPECTED_SORT =
    Arrays.asList("A", "car", "Bar", "Bill");
  private static final BigDecimal[] EXPECTED_LEN = new BigDecimal[]{
    BigDecimal.valueOf(3),
    BigDecimal.valueOf(1),
    BigDecimal.valueOf(4),
    BigDecimal.valueOf(3)
  };

  private List<String> strings;
  private final AtomicInteger counter = new AtomicInteger(0);

  // ------------------------------

  @Before
  public void before() {
    strings = Arrays.asList("car", "A", "Bill", "Bar");
  }

  @Test
  public void testLambdaFull() {
    Collections.sort(strings,
      (String str1, String str2) -> Integer.compare(str1.length(), str2.length()));
    assertEquals(EXPECTED_SORT, strings);
  }

  @Test
  public void testLambdaInferred() {
    Collections.sort(strings,
      (str1, str2) -> Integer.compare(str1.length(), str2.length()));
    assertEquals(EXPECTED_SORT, strings);
  }

  @Test
  public void testComparatorLambda() {
    Collections.sort(strings, Comparator.comparingInt(str -> str.length()));
    assertEquals(EXPECTED_SORT, strings);
  }

  @Test
  public void testComparatorMethodRefClassInst() {
    // ** Class::instanceMethod **
    Collections.sort(strings, Comparator.comparingInt(String::length));
    assertEquals(EXPECTED_SORT, strings);
  }

  @Test
  public void testComparatorMethodRefObjectInst() throws Exception {
    Thread.currentThread().setName("MAIN");

    // object::instanceMethod & lambda no param
    Thread th = new Thread(() -> Collections.sort(strings, this::instComp));
    th.start();
    th.join();

    assertEquals(EXPECTED_SORT, strings);
    assertEquals("MAIN", Thread.currentThread().getName());
  }

  @Test
  public void testComparatorMethodRefEnclosingClassInst() throws Exception {
    Thread th = new Thread(new MyRunner(strings));
    th.start();
    th.join();

    assertEquals(EXPECTED_SORT, strings);
    assertEquals(1, counter.get());
  }

  @Test
  public void testConstructorReference() {
    BigDecimal[] allLength = strings
      .stream()
      .map(String::length)          // Class::instanceMethod
      .map(BigDecimal::new)         // Class::constructor
      .toArray(BigDecimal[]::new);  // Array::constructor

    assertArrayEquals(EXPECTED_LEN, allLength);
  }

  // ------------------------------

  private int instComp(String a, String b) {
    Thread.currentThread().setName("INST_COMP");

    return Integer.compare(a.length(), b.length());
  }

  // ------------------------------

  class MyRunner implements Runnable {
    private List<String> list;

    MyRunner(List<String> list) {
      this.list = list;
    }

    @Override
    public void run() {
      // EnclosingClass.this::instanceMethod
      list.sort(TestFunP.this::instComp);

      assertEquals("INST_COMP", Thread.currentThread().getName());

      counter.incrementAndGet();
    }
  }
}

Notes

  • Since Java 8, we can use LIST_VAR.sort() instead of Collections.sort(LIST_VAR).
  • For number comparison use <NUMBER CLASS>.compare() (e.g. Integer.compare())

    Don’t compute x - y for comparison since computation can overflow for large operands of opposite sign!

Method & Constructor Reference

Consider lambda as (v1, v2, ...) -> return_type in general functional programming idiom. Now, instead of an in-line code, a method with the same syntax (return_type F(v1, v2, ...))can be referred conformed to one of the following syntax:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

    the first parameter becomes the target of the method, e.g. String::compareToIgnoreCase is the same as (x, y) -> x.compareToIgnoreCase(y) or in above example String::length is the same as x -> x.length()

  • this::instanceMethod
  • super::instanceMethod
  • EnclosingClass.this::instanceMethod
  • EnclosingClass.super::instanceMethod
  • Class::new

    constructor reference

  • Class[]::new

    constructor reference with array type (int[]::new is equivalent to the lambda expression x -> new int[x]) this is useful for libraries with method returning array such as stream.toArrey(), so you can call stream.toArray(CLASS[]::new)

The :: operator separates the method name.

More of Comparator class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;

public class TestComparator {
  @Test
  public void testComparator() {
    List<Employee> list = Arrays.asList(
      new Employee("John", 5000),
      new Employee("Jack", 5000),
      new Employee("Bill", 3000));

    List<String> expected = Arrays.asList(
      "Jack, 5000",
      "John, 5000",
      "Bill, 3000");

    // try to sort: first by salary descending, and then by name ascending
    list.sort(Comparator
      .comparing(Employee::getSalary, (o1, o2) -> Double.compare(o2, o1))
      .thenComparing(Employee::getName));

    List<String> actual = list.stream()
      .map(Employee::toString)
      .collect(Collectors.toList());

    assertEquals(expected, actual);
  }

  // ------------------------------

  class Employee {
    private String name;
    private Integer salary;

    Employee(String name, Integer salary) {
      this.name = name;
      this.salary = salary;
    }

    String getName() {
      return name;
    }

    Integer getSalary() {
      return salary;
    }

    @Override
    public String toString() {
      return getName() + ", " + getSalary();
    }
  }
}

Working with Files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final Consumer<File> printFileName =
  f -> System.out.println(f.isDirectory() ?
    f.getName() + "/" :
    f.getName()
  );

File file = new File("<A_DIR>");
List<File> files = Arrays.asList(file.listFiles());
files.forEach(printFileName);

System.out.println("---");

// sort files, directories first, and then by name in each group
files.sort((o1, o2) -> {
    int byDir = Boolean.compare(o2.isDirectory(), o1.isDirectory());
    if (byDir == 0) {
        return o1.getName().compareToIgnoreCase(o2.getName());
    }
    return byDir;
});
files.forEach(printFileName);

Interfaces with default and static methods

  • From Java 8
  • An interface can have multiple static methods. Now it is possible to put the factory method in the interface!
  • Any methods in an interface can have default implementation, e.g. suppose the default implementation for isEmpty in Collection interface:
    1
    2
    3
    4
    5
    6
    7
    
    public interface Collection {
      int size();
    
      default boolean isEmpty { return size() == 0; }
    
      ...
    }
    
  • In case of default method conflicts, it must be resolved manually:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    interface Person { default String getName() {...} }
    
    interface Named { default String getName() {...} }
    
    class Employee implements Person, Named {
      public String getName() {
        return Person.super.getName();  // call the default method explicitly
      }
    }
    

    Note: Concrete superclass methods mask default methods

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    interface Named { default String getName() {...} }
    
    class Employee { public String getName() {...} }
    
    class Manager extends Employee implements Named { // 
      public String toString() {
        return getName(); // Employee.getName() is called!
      }
      ...
    }