Saturday, June 10, 2017

Java 8 : Lambdas

What is a Lambda ?
A lambda expression represents an anonymous function. It comprises of a set of parameters, a lambda operator (->) and a function body.

The Parameters
  • A lambda expression can receive zero, one or more parameters.
  • The type of the parameters can be explicitly declared or it can be inferred from the context.
  • Parameters are enclosed in parentheses and separated by commas.
  • Empty parentheses are used to represent an empty set of parameters.
  • When there is a single parameter, if its type is inferred, it is not mandatory to use parentheses.
The Body
  • The body of the lambda expression can contain zero, one or more statements.
  • When there is a single statement curly brackets are not mandatory and the return type of the anonymous function is the same as that of the body expression.
  • When there is more than one statement then these must be enclosed in curly brackets (a code block) and the return type of the anonymous function is the same as the type of the value returned within the code block, or void if nothing is returned.
What is a Java Functional Interface
In Java, a functional interface is basically an interface with a single abstract method. This kind of interfaces are also known as SAM (Single Abstract Method) types.

Before Java 8 there were already several interfaces compatible with this idea:

public interface Comparator {
    int compare(T o1, T o2);
}
public interface Runnable {
    public void run();
}


Many new functional interface are defined in java8 , they are in new java.util.function package.

Why We Need Lambdas  : Theoretical Ans ?
We iterate the collection externally, explicitly pulling out and processing the items one by one.It will be far better if i could do it internally.

Doing it externally is inefficient. Using an internal iteration the JIT compiler could optimize it processing the items in parallel or in a different order. These optimizations are impossible if we iterate the collection externally as we are used to do in Java and more in general with the imperative programming.

Think of it in this way, There are two ways to ask a child to pick up toys
  1. first : Point to each and every toy and ask the child to pick up = inefficient = external iteration
  2. second :  Ask child to pick up all toys : he/she can choose where to start / stop etc = efficient
Why We Need Lambdas : Cleaner Code

// Old : Pre Java 8
button.addActionListener(new ActionListener() { 
    @Override
    public void actionPerformed(ActionEvent e) { 
        doSomethingWith(e); 
    }
});

// New : In Java 8
button.addActionListener(e -> doSomethingWith(e))

Why We Need Lambdas : Allows Writing Generic Function

Suppose we have a business requirement
  1. Started with a simply a sumAll integers
  2. Modified to sumAll odd integers
  3. Modified to sumAll integers greater than 5
How can we implement it in clean way ?

By implementing a lambda for functional interface 'Predicate' that comes in java.utilfunction package


public interface Predicate<T> {
    boolean test(T t);
}

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;
    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

sumAll(numbers, n -> true);
sumAll(numbers, n -> n % 2 != 0);
sumAll(numbers, n -> n > 5);

So we should agree it allows us to write generic functions , with minimum code duplicacy.

Why We Need Lambdas : Efficiency Through Lazy Evaluation
Suppose you want to
  1. Get all even numbers in the list
  2. Double them 
  3. and finally print the first one bigger than 5
Streams can help a lot to solve these problems efficiently. You can create a Stream from any Collection by invoking the new stream() method on it. However Streams differ from Collections in several ways:

  • No storage: Streams don't have storage for values; they carry values from a data structure through a pipeline of operations.
  • Functional in nature: an operation on a stream produces a result, but does not modify its underlying data source. A Collection can be used as a source for a stream.
  • Laziness-seeking: many stream operations, such as filtering, mapping, sorting, or duplicate removal can be implemented lazily, meaning we only need to examine as many elements from the stream as we need to find the desired answer.
  • Bounds optional: there are many problems that are sensible to express as infinite streams, letting clients consume values until they are satisfied. Collections don't let you do this, but streams do
When you write a Lambda : What Happens Internally ?
  • You write what looks like a function
    –Arrays.sort( testStrings, (s1, s2) -> s1.length() - s2.length() );
    –taskList.execute(( ) -> downloadSomeFile());
    –someButton.addActionListener(event -> handleButtonClick( ));
    –double d = MathUtils.integrate(x -> x*x , 0, 100, 1000);
  • From The code in red , You get an instance of a class that implements the interface  that was expected in that place
Basically... Replace...


new SomeInterface() {
    @Override
    public someType someMethod (args) { body }
});

With this
(args) -> { body } 

So to put it in another way ...  its a short hand way of describing the body of the method that the interface expects.

To help you better picturize what we are talking about , have a look at this

When body has only a single return statement , then you can replace.

Or better put it this way

 So you can basically focus on the code that really matter , and not need to see the boiler plate everytime.

If the interface has only argument passed in function as a parameter then you can also omit the brackets.

No comments: