Sunday, August 6, 2017

Java 8 - Lambdas

Behavior parameterization

Behavior parameterization is the ability for a method to take multiple different behaviors (or strategies) as parameters and use them internally to accomplish different behaviors.
Behavior parameterization lets you make your code more adaptive to changing requirements and saves on engineering efforts in the future.
Passing code is a way to give new behaviors as arguments to a method. But it’s verbose prior to Java 8.
Anonymous classes helped a bit before Java 8 to get rid of the verbosity associated with declaring multiple concrete classes for an interface that are needed only once.
The Java API contains many methods that can be parameterized with different behaviors, which include sorting, threads, and GUI handling.

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

Lambda expression


A lambda expression can be understood as a concise representation of an anonymous function that can be passed around: it 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.
Let’s break it down:
  • Anonymous— We say anonymous because it doesn’t have an explicit name like a method would normally have: less to write and think about!
  • Function— We say function because a lambda isn’t associated with a particular class like a method is. But like a method, a lambda has a list of parameters, a body, a return type, and a possible list of exceptions that can be thrown.
  • Passed around— A lambda expression can be passed as argument to a method or stored in a variable.
  • Concise— You don’t need to write a lot of boilerplate like you do for anonymous classes.

A lambda expression is composed of parameters, an arrow, and a body.

Runnable r = () -> System.out.println("Hello!");
Runnable r = () -> {};
Callable<String> c () -> "Samara";
Callable<String> c () -> "Samara" + 12;
(Integer i) -> {return "Samara" + 22;} // return is control-flow statement. it has to be in curly braces
(String s) -> {return "S";} // cannot stay just "S" when you have control-flow statement.

You can use a lambda expression in the context of a functional interface.


Functional interface

Functional interface is an interface that specifies exactly one abstract method.
@FunctionalInterface - optional annotation used to indicate that the interface is intended to be a functional interface.

Examples:
  • java.util.Comparator<T>
  • java.lang.Runnable
  • java.util.concurrent.Callable<V>
  • java.util.function.Predicate<T>

@FunctionalInterface
public interface Predicate<T> {


    /** Evaluates this predicate on the given argument. */
    boolean test (T t);
}

You might want to use this interface when you need to represent a boolean expression that uses an object of type T .

@FunctionalInterface
public interface Consumer<T> {

    /** Performs this operation on the given argument. */
    void accept(T t);
}
You might use this interface when you need to access an object of type T and perform some operations on it

@FunctionalInterface
public interface Function<T, R> {

    /** Applies this function to the given argument. */
    R apply(T t);
}
You might use this interface when you need to define a lambda that maps information from an input object to an output.

@FunctionalInterface
public interface Supplier<T> {

    /** Gets a result. */
    T get();
}

Interfaces can also have default methods (that is, a method with a body that provides some default implementation for a method in case it isn’t implemented by a class). An interface is still a functional interface if it has many default methods as long as it specifies only one abstract method.

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 (more technically speaking, an instance of a concrete implementation of the functional interface). You can achieve the same thing with an anonymous inner class, although it’s clumsier.

Primitive specializations

Java 8 brings a specialized version of the functional interfaces in order to avoid autoboxing operations when the inputs or outputs are primitives. To avoid boxing, use IntPredicate, not Predicate<Integer>.

Exceptions

Note that none of the functional interfaces allow for a checked exception to be thrown. You have two options if you need a lambda expression to throw an exception: define your own functional interface that declares the checked exception, or wrap the lambda with a try/catch block (and you can re-throw runtime exception).

Restrictions on local variables  

closure is an instance of a function that can reference nonlocal variables of that function with no restrictions. For example, a closure could be passed as argument to another function. It could also access and modify variables defined outside its scope.
Now Java 8 lambdas and anonymous classes do something similar to closures: they can be passed as argument to methods and can access variables outside their scope. But they have a restriction: they 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. This restriction exists because local variables live on the stack and are implicitly confined to the thread they’re in. Allowing capture of mutable local variables opens new thread-unsafe possibilities, which are undesirable (instance variables are fine because they live on the heap, which is shared across threads).

Method references

Method references let you reuse existing method definitions and pass them just like lambdas. In some cases they appear more readable and feel more natural than using lambda expressions.

Lambda
Method reference equivalent
(Apple a) -> a.getWeight()
Apple::getWeight
() -> Thread.currentThread().dumpStack()
Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)
String::substring
(String s) -> System.out.println(s)
System.out::println

You can think of method references as syntactic sugar for lambdas that refer only to a single method because you write less to express the same thing.

Recipe for constructing method references

There are three main kinds of method references:
  1. A method reference to a static method (for example, the method parseInt of Integer, written Integer::parseInt)
  2. A method reference to an instance method of an arbitrary type (for example, the method length of a String, written String::length)
  3. A method reference to an instance method of an existing object (for example, suppose you have a local variable expensiveTransaction that holds an object of type Transaction, which supports an instance method getValue; you can write expensiveTransaction::getValue)


Constructor references

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.
Examples (normally you would not use new on Strings or on Integer class!)

String s1 = new String();
Supplier<String> createNewString = String::new;
String s2 = createNewString.get();

Integer i1 = new Integer(12);
IntFunction<Integer> createNewInteger = Integer::new;
Integer i2 = createNewInteger.apply(12);



Lambdas in practice

Pear has weight property.

List<Pear> inventory = new ArrayList<>();
        inventory.addAll(Arrays.asList(new Pear(70), new Pear(135), new Pear(110)));
   
        // Use an anonymous class
        inventory.sort(new Comparator<Pear>() {
            public int compare(Pear p1, Pear p2){
                return p1.getWeight().compareTo(p2.getWeight());
        }});
   
        // Use lambda expressions
        inventory.sort((Pear p1, Pear p2) -> p1.getWeight().compareTo(p2.getWeight()));
   
        // Java compiler could infer the types of the parameters of a lambda expression by using the context in which the lambda appears
        inventory.sort((p1, p2) -> p1.getWeight().compareTo(p2.getWeight()));
   
        // Comparator has a static helper method called comparing that takes a Function extracting a Comparable key and produces a Comparator object
        inventory.sort(Comparator.comparing((a) -> a.getWeight()));
   
        // Use method references

        // import static java.util.Comparator.comparing;
        inventory.sort(comparing(Pear::getWeight));


        // chaining
        inventory.sort(comparing(Pear::getWeight).reversed());
        inventory.sort(comparing(Pear::getWeight).thenComparing(Pear::getCountry));

Ref:
Java 8 in Action book
Java 8: Behavior parameterization

No comments:

Post a Comment