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
- Such interface can be tagged by
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 ofCollections.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 exampleString::length
is the same asx -> 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 expressionx -> new int[x]
) this is useful for libraries with method returning array such asstream.toArrey()
, so you can callstream.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 forisEmpty
inCollection
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! } ... }