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).

Java 8 in Action -- Chapter 2. Passing code with behavior parameterization

1.  Behavior parameterization is a software development pattern that lets you handle frequent requirement changes. In a nutshell, it means taking a block of code and making it available without executing it. This block of code can be called later by other parts of your programs, which means that you can defer the execution of that block of code. For instance, you could pass the block of code as an argument to another method that will execute it later. As a result, the method’s behavior is parameterized based on that block of code.

2.  Strategy design pattern lets you define a family of algorithms, encapsulate each algorithm (called a strategy), and select an algorithm at run-time.

3.  Behavior parameterization means: the ability to tell a method to take multiple behaviors (or strategies) as parameters and use them internally to accomplish different behaviors.

4.  Anonymous classes, which let you declare and instantiate a class at the same time. In other words, they allow you to create ad hoc implementations.

5.  In Java 8, a List comes with a sort method (you could also use Collections.sort). The behavior of sort can be parameterized using a java.util.Comparator object.

Java 8 in Action -- Chapter 1. Java 8: why should you care?

1.  The changes to Java 8 enable you to write more concise code and provides a simpler use of multicore processors.

2.  The three programming concepts added to Java 8 are stream processing, passing code to methods with behavior parameterization and parallelism with no shared mutable data.

3.  Java 8 provides a new API (called Streams) that supports many parallel operations to process data and resembles the way you might think in database query languages—you express what you want in a higher-level manner, and the implementation (here the Streams library) chooses the best low-level execution mechanism.

4.  The Java 8 feature of passing code to methods (and also being able to return it and incorporate it into data structures) also provides access to a whole range of additional techniques that are commonly referred to as functional-style programming.

5.  A stream is a sequence of data items that are conceptually produced one at a time.

6.  Java 8 adds a Streams API in java.util.stream. Stream<T> is a sequence of items of type T. You can think of it as a fancy iterator for now. The Streams API has many methods that can be chained to form a complex pipeline just like Unix commands are chained:

cat file1 file2  |  tr "[A-Z]"  "[a-z]"  |  sort  |  tail -3

which (supposing file1 and file2 contain a single word per line) prints the three words from the files that appear latest in dictionary order, after first translating them to lowercase.(Unix cat creates a stream by concatenating two files, tr translates the characters in a stream, sort sorts lines in a stream, and tail -3 gives the last three lines in a stream. )

7.  You can now program in Java 8 at a higher level of abstraction, structuring your thoughts of turning a stream of this into a stream of that. Java 8 can transparently run your pipeline of Stream operations on several CPU cores on disjoint parts of the input—this is parallelism almost for free instead of hard work using Threads.

8.  You must provide behavior passed to stream methods that is safe to execute concurrently on different pieces of the input. Typically this means writing code that doesn’t access shared mutable data to do its job. Sometimes these are referred to as pure functions or side-effect-free functions or stateless functions.

9.  Values which can’t be passed around during program execution, are second-class values.

10.  Java 8 introduces method references by :: syntax : File::isHidden

11.  As well as allowing (named) methods to be first-class values, Java 8 allows a richer idea of functions as values, including lambda (or anonymous functions). For example, you can now write (int x) -> x + 1 to mean “the function that, when called with argument x, returns the value x + 1.”

12.  The word predicate is often used in mathematics to mean something function-like that takes a value for an argument and returns true or false. Java 8 would also allow you to write Function<Apple,Boolean> but using Predicate<Apple> is more standard (and slightly more efficient because it avoids boxing a boolean into a Boolean).

13.  Streams API provides a very different way to process data in comparison to the Collections API. Using a collection, you’re managing the iteration process yourself. You need to iterate through each element one by one using a for-each loop and then process the elements. We call this way of iterating over data external iteration. In contrast, using the Streams API, you don’t need to think in terms of loops at all. The data processing happens internally inside the library. We call this idea internal iteration.

14.  Collections is mostly about storing and accessing data, whereas Streams is mostly about describing computations on data. The first design motivator is that there are many data processing patterns that occur over and over again and that would benefit from forming part of a library: filtering data based on a criterion, extracting data , or grouping data , and so on. The second motivator is that such operations can often be parallelized.

15.  There are actually two magic bullets for parallelism in Java. First, the library handles partitioning—breaking down a big stream into several smaller streams to be processed in parallel for you. Second, this parallelism almost for free from streams works only if the methods passed to library methods don’t interact, for example, by having mutable shared objects.

16.  Default methods are added to Java 8 largely to support library designers by enabling them to write more evolvable interfaces. An interface can now contain method signatures for which an implementing class doesn’t provide an implementation. The missing method bodies are given as part of the interface (hence default implementations) rather than in the implementing class.

17.  In Java 8 there’s an Optional<T> class that, if used consistently, can help you avoid NullPointer exceptions. It’s a container object that may or not contain a value. Optional<T> includes methods to explicitly deal with the case where a value is absent, and as a result you can avoid NullPointerException. In other words, it uses the type system to allow you to indicate when a variable is anticipated to potentially have a missing value.

18.  For more complex data types, pattern matching can express programming ideas more concisely compared to using if-then-else. Unfortunately, Java 8 doesn’t have full support for pattern matching. you can think of pattern matching as an extended form of switch that can decompose a data type into its components at the same time.