2017年3月11日星期六

Java 8 in Action -- Chapter 3. Lambda expressions

1.  You can think of lambda expressions as anonymous functions, basically methods without declared names, but which can also be passed as arguments to a method as you can with an anonymous class.

2.  A lambda expression doesn’t have a name, but it has a list of parameters, a body, a return type, and also possibly a list of exceptions that can be thrown.

3.  Lambdas technically don’t let you do anything that you couldn’t do prior to Java 8.

4.  A lambda has three parts:

  • A list of parameters separated by comma in parenthesis
  • An arrow "->" that separates the list of parameters from the body of the lambda.
  • The body of the lambda -- the expression which is considered the lambda’s return value.

5.  The basic syntax of a lambda is either

(parameters) -> expression

or (note the curly braces for statements)

(parameters) -> { statements; }

6.  In a nutshell, a functional interface is an interface that specifies exactly one abstract method. An interface is still a functional interface if it has many default methods as long as it specifies only one abstract method.

7.  You can use a lambda expression in the context of a functional interface. Lambda expressions let you provide the implementation of the abstract method of a functional interface directly inline and treat the whole expression as an instance of a functional interface.

8.  The signature of the abstract method of the functional interface essentially describes the signature of the lambda expression. We call this abstract method a function descriptor.

9.  A lambda expression can be assigned to a variable or passed to a method expecting a functional interface as argument, provided the lambda expression has the same signature as the abstract method of the functional interface.

10.  @FunctionalInterface annotation is used to indicate that the interface is intended to be a functional interface. The compiler will return a meaningful error if you define an interface using the @FunctionalInterface annotation and it isn’t a functional interface.

11.  A recurrent pattern in resource processing (for example, dealing with files or databases) is to open a resource, do some processing on it, and then close the resource. The setup and cleanup phases are always similar and surround the important code doing the processing. This is called the execute around pattern.

12.  The Java library designers for Java 8 have helped you by introducing several new functional interfaces inside the java.util.function package:

Functional interface
Function descriptor
Primitive specializations
Predicate<T>

T -> boolean
IntPredicate, LongPredicate, DoublePredicate
Consumer<T>
T -> void
IntConsumer, LongConsumer, DoubleConsumer
Function<T, R>
T -> R
IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T>
Supplier<T>
() -> T
BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator<T>
T -> T
IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator<T>
(T, T) -> T
IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L, R>
(L, R) -> boolean

BiConsumer<T, U>
(T, U) -> void
ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>
BiFunction<T, U, R>
(T, U) -> R
ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U>

13.  The java.util.function.Predicate<T> interface defines an abstract method named test that accepts an object of generic type T and returns a boolean. You might want to use this interface when you need to represent a boolean expression that uses an object of type T.

14.  The java.util.function.Consumer<T> interface defines an abstract method named accept that takes an object of generic type T and returns no result (void). You might use this interface when you need to access an object of type T and perform some operations on it.

15.  Java 8 brings a specialized version of the functional interfaces to avoid autoboxing operations when the inputs or outputs are primitives. i.e. IntPredicate.

16.  In general, the names of functional interfaces that have a specialization for the input type parameter are preceded by the appropriate primitive type: DoublePredicate, IntConsumer, LongBinaryOperator, IntFunction, and so on. The Function interface has also variants for the output type parameter: ToIntFunction<T>, IntToDoubleFunction, and so on.

17.  The type of a lambda is deduced from the context in which the lambda is used. The type expected for the lambda expression inside the context is called the target type.

18.  If a lambda has a statement expression as its body, it’s compatible with a function descriptor that returns void (provided the parameter list is compatible too):

Consumer<String> b = s -> list.add(s);

19.  Lambda expressions can get their target type from an assignment context, method invocation context (parameters and return), and a cast context. Below code cannot compile because the lambda expression cannot get its target type:

Object o = () -> {System.out.println("Tricky example"); };

20.  The Java compiler can infer the types of the parameters of a lambda and when a lambda has just one parameter whose type is inferred, the parentheses surrounding the parameter name can also be omitted:

 Predicate<String> p = s -> list.add(s);

21.  Lambda expressions are also allowed to use local variables in an outer scope just like anonymous classes can. They’re called capturing lambdas. But local variables have to be explicitly declared final or are effectively final. In other words, lambda expressions can capture local variables that are assigned to them only once (before the lambda expression). (Note: capturing an instance variable can be seen as capturing the final local variable this.)

22.  Instance variables are stored on the heap, whereas local variables live on the stack. If a lambda could access the local variable directly and the lambda were used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it. Hence, Java implements access to a free local variable as access to a copy of it rather than access to the original variable. This makes no difference if the local variable is assigned to only once—hence the restriction.

23.  A closure is an instance of a function that can reference non-local variables of that function with no restrictions.
 
24.  Lambdas and anonymous class can’t modify the content of local variables of a method in which the lambda is defined. Those variables have to be implicitly final. It helps to think that lambdas close over values rather than variables.

25.  A method reference lets you create a lambda expression from an existing method implementation. When you need a method reference, the target reference is placed before the delimiter :: and the name of the method is provided after it. For example, Apple::getWeight is a method reference to the method getWeight defined in the Apple class. The method reference is shorthand for the lambda expression (Apple a) -> a.getWeight().

26. There are three main kinds of method references:

  • A method reference to a static method. And the parameters of the lambda are taken as the arguments of the method call. i.e. Integer::parseInt
  • A method reference to an instance method of an arbitrary type. You’re referring to a method to an object that will be supplied as the first parameter of the lambda. And the other parameters of the lambda are taken as the arguments of the method call. the lambda expression (String s , int i) -> s.subString(i) can be rewritten as String::subString.
  • A method reference to an instance method of an existing object. you’re calling a method in a lambda to an external object that already exists. And the parameters of the lambda are taken as the arguments of the method call. the lambda expression (String s) -> System.out.println(s) can be rewritten as System.out::println.

27. The shorthand rules to refactor a lambda expression to an equivalent method reference follow simple recipes:


28.  You can create a reference to an existing constructor using its name and the keyword new as follows: ClassName::new. It works similarly to a reference to a static method.

29. Comparator has a static helper method called comparing that takes a Function extracting a Comparable key and produces a Comparator object.

30.  The Comparator interface includes a default method reversed that imposes the reverse ordering of a given Comparator. The thenComparing method allows you to provide a second Comparator to further refine the comparison :

inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));


31.  The Predicate interface includes three methods that let you reuse an existing Predicate to create more complicated ones: negate, and, and or.

32.  The Function interface comes with two default methods: andThen and compose, which both return an instance of Function that first applies a given function to an input and then applies another function to the result of that application. f.andthen(g) is equivalent to g.compose(f) which means g(f).

没有评论:

发表评论