In Java 8, functional interfaces, lambdas, and method references were added to make it easier to create function objects.
The streams API support for processing sequences of data elements.
In this chapter, we discuss how to make best use of these facilities.
1. Prefer lambdas to anonymous classes
1.1 Introduce
Historically, interfaces (or, rarely, abstract classes) with a single abstract method were used as function types.
Their instances, known as function objects, represent functions or actions.
In Java 8, the language formalized the notion that interfaces with a single abstract method are special and deserve special treatment.
These interfaces are now known as functional interfaces.
1.2 Examples
(1)Example one
List<String> words = Lists.newArrayList();
// Anonymous class instance as a function object - obsolete(废弃)!
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
// Lambda expression(or lambdas for short) as function object (replaces anonymous class)
// The compiler deduces these types from context, using a process known as type inference
Collections.sort(words,
(s1, s2) -> Integer.compare(s1.length(), s2.length()));
// A comparator construction method is used in place of a lambda
Collections.sort(words, comparingInt(String::length));
// Taking advantage of the sort method that was added to the List interface in Java 8
words.sort(comparingInt(String::length));
(2)Example Two
// Enum type with constant-specific class bodies & data (Item 34)
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; } },
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; } },
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
// Enum with function object fields & constant-specific behavior
public enum Operation {
PLUS ("+", (x, y) -> x + y),
MINUS ("-", (x, y) -> x - y),
TIMES ("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y);
private final String symbol;
private final DoubleBinaryOperator op;
Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
@Override public String toString() { return symbol; }
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
@FunctionalInterface public interface DoubleBinaryOperator {
/**
* Applies this operator to the given operands.
*
* @param left the first operand
* @param right the second operand
* @return the operator result
*/
double applyAsDouble(double left, double right); }
1.3 Tips
(1) Omit the types of all lambda parameters unless their presence makes your program clearer.
(2) Lambdas lack names and documentation; if a computation isn’t self-explanatory, or exceeds a few lines, don’t put it in a lambda
(3)A few things you can do with anonymous classes that you can’t do with lambdas
- Create an instance of an abstract class
- Create instances of interfaces with multiple abstract methods
- The this keyword refers to the anonymous class instance , If you need access to the function object from within its body, then you must use an anonymous class
(3)You should rarely, if ever, serialize a lambda (or an anonymous class instance)
If you have a function object that you want to make serializable, such as a Comparator, use an instance of a private static nested class
(4) Don’t use anonymous classes for function objects unless you have to create instances of types that aren’t functional interfaces
2.Prefer method references to lambdas
2.1 Introduce
map.merge(key, 1, (count, incr) -> count + incr);
map.merge(key, 1, Integer::sum);
(1)A static method (ClassName::methName)
(2)An instance method of a particular object (instanceRef::methName)
(3)A super method of a particular object (super::methName)
(4)An instance method of an arbitrary object of a particular type (ClassName::methName)
(4)A class constructor reference (ClassName::new)
(5)An array constructor reference (TypeName[]::new)
2.2 Tips
(1)Where method references are shorter and clearer, use them; where they aren’t, stick with lambdas.
service.execute(() -> action());
service.execute(GoshThisClassNameIsHumongous::action);
3.Favor the use of standard functional interfaces
The java.util.function package provides a large collection of standard functional interfaces for your use.
There are forty-three interfaces in java.util.Function
3.1 Examples
// 使用Supplier接口实现方法,返回一个随机值
Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt();
}
};
// 使用lambda表达式,
supplier = () -> new Random().nextInt();
// 使用方法引用
Supplier<Double> supplier2 = Math::random;
3.2 basic interfaces

UnaryOperator:Represents an operation on a single operand that produces a result of the same type as its operand;
BinaryOperator:Represents an operation upon two operands of the same type, producing a result of the same type as the operands
Predicate:Represents a predicate (boolean-valued function) of one argument.
Function:Represents a function that accepts one argument and produces a result.
Supplier:Represents a supplier of results.
Consumer:Represents an operation that accepts a single input argument and returns no result
3.3 Variants
(1)There are also three variants of each of the six basic interfaces to operate on the primitive types int, long, and double.
For example, like IntPredicate which takes an int.
(2)There are nine additional variants of the Function interface, for use when the result type is primitive.
- Function with SrcToResult for example LongToIntFunction(six variants )
- Function with ToObj, for example DoubleToObjFunction (three variants).
(3)BiPredicate<T,U>, BiFunction<T,U,R>, and BiConsumer<T,U>
(4)ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, and ToDoubleBiFunction<T,U>.
(5)ObjDoubleConsumer, ObjIntConsumer, and ObjLongConsumer
(6)BooleanSupplier
3.4 Tips
(1) If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.
(2) Don’t be tempted to use basic functional interfaces with boxed primitives instead of primitive functional interfaces
The performance consequences of using boxed primitives for bulk operations can be deadly
(3) Always annotate your functional interfaces with the @FunctionalInterface annotation.
This annotation type is similar in spirit to @Override. It is a statement of programmer intent that serves three purposes
- It tells readers of the class and its documentation that the interface was designed to enable lambdas
- It keeps you honest because the interface won’t compile unless it has exactly one abstract method
- It prevents maintainers from accidentally adding abstract methods to the interface as it evolves
4.Use streams judiciously
The streams API was added in Java 8 to ease the task of performing bulk operations, sequentially or in parallel.
4.1 Disadvantages of streams
(1)Overusing streams makes programs hard to read and maintain.
public class Anagrams {
public static void main(String[] args) throws IOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
try (Stream<String> words = Files.lines(dictionary)) {
words.collect(
groupingBy(word -> word.chars().sorted()
.collect(StringBuilder::new,
(sb, c) -> sb.append((char) c),
StringBuilder::append).toString()))
.values().stream()
.filter(group -> group.size() >= minGroupSize)
.map(group -> group.size() + ": " + group)
.forEach(System.out::println);
}
}
}
// Tasteful use of streams enhances clarity and conciseness
public class Anagrams {
public static void main(String[] args) throws IOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
try (Stream<String> words = Files.lines(dictionary)) {
words.collect(groupingBy(word -> alphabetize(word))) .values().stream()
.filter(group -> group.size() >= minGroupSize)
.forEach(g -> System.out.println(g.size() + ": " + g));
}
}
private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
}
(2) Bad deal with char values
// print 721011081081113211911111410810033
"Hello world!".chars().forEach(System.out::print);
// print Hello world!
"Hello world!".chars().forEach(x -> System.out.print((char) x));
(3)From a code block, you can read or modify any local variable in scope; from a lambda, you can only read final or effectively final variables [JLS 4.12.4], and you can’t modify any local variables.
JLS:Java Language Specification
HTML Version:docs.oracle.com/javase/spec…
JLS 4.12.4: A variable can be declared final. A final variable may only be assigned to once. It is a compile-time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment
(4) From a code block, you can return from the enclosing method, break or continue an enclosing loop, or throw any checked exception that this method is declared to throw; from a lambda you can do none of these things.
4.2 How to use it correctly
(1) In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines.
(2) Using helper methods is even more important for readability in stream pipelines than in iterative code
(3) refrain from using streams to process char values.
(4) refactor existing code to use streams and use them in new code only where it makes sense to do so.
Good time to use streams
- Uniformly transform sequences of elements(eg: .map(), .peek())
- Filter sequences of elements(eg: .filter()
- Combine sequences of elements using a single operation (for example to add them, concatenate them, or compute their minimum)(eg: .min(), .mapToLong()))
- Accumulate sequences of elements into a collection, perhaps grouping them by some common attribute(eg: .groupingBy())
- Search a sequence of elements for an element satisfying some criterion(eg: .anyMatch())
(5) If you’re not sure whether a task is better served by streams or iteration, try both and see which works better.
5.Prefer side-effect-free functions in streams
Streams isn’t just an API, it’s a paradigm based on functional programming
5.1 Tips
(1) The forEach operation should be used only to report the result of a stream computation, not to perform the computation.
(2) It is customary and wise to statically import all members of Collectors because it makes stream pipelines more readable.
The collectors for gathering the elements of a stream into a true Collection are straightforward. There are three such collectors: toList(), toSet(), and toCollection(collectionFactory)
// Pipeline to get a top-ten list of words from a frequency table
List<String> topTen = freq.keySet()
.stream()
.sorted(comparing(freq::get)
.reversed())
.limit(10)
.collect(toList());
(3) There is never a reason to say collect(counting()).
The same functionality is available directly on Stream, via the count method
6.Prefer Collection to Stream as a return type
6.1 Collection or an appropriate subtype is generally the best return type for a public, sequence- returning method.
6.2 Do not store a large sequence in memory just to return it as a collection.
7.Use caution when making streams parallel
7.1 Problems
(1) parallelizing a pipeline is unlikely to increase its performance if the source is from Stream.iterate, or the intermediate operation limit is used.
(2) Not only can parallelizing a stream lead to poor performance, including liveness failures; it can lead to incorrect results and unpredictable behavior
7.2 Use it correctly
(1) Performance gains from parallelism are best on streams over ArrayList, HashMap, HashSet, and ConcurrentHashMap instances; arrays; int ranges; and long ranges.
- What these data structures have in common is that they can all be accurately and cheaply split into subranges of any desired sizes, which makes it easy to divide work among parallel threads
- Another important factor that all of these data structures have in common is that they provide good-to-excellent locality of reference when processed sequen- tially: sequential element references are stored together in memory
- If you write your own Stream, Iterable, or Collection implementation and you want decent parallel performance, you must override the spliterator method and test the parallel performance of the resulting streams extensively.
(2) Under the right circumstances, it is possible to achieve near-linear speedup in the number of processor cores simply by add- ing a parallel call to a stream pipelin
// Prime-counting stream pipeline - parallel version.
static long pi(long n) {
return LongStream.rangeClosed(2, n)
.parallel()
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}