Wednesday 17 August 2016

Java 8 Lambda

Definition:

An Lambda expression can be understood as a concise representation of an anonymous function that can be passed around.

Functional Interfaces provided by Java 8:

Functional InterfaceFunctional DescriptorMethod Name
Predicate<T>T -> booleantest
Consumer<T>T -> voidaccept
Function<T,R>T -> Rapply
Supplier<T>() -> Tget
UnaryOperator<T>T -> T
identity

The UnaryOperator internally extends Function<T,T>. All these have there respective Bi Functions.

Primitive Specialization of Functional Interfaces:

The issue with the above defined functional interface is that in case of primitive types these needs to boxed into an object and this causes usage of more memory and additional memory lookups to fetch the wrapped primitive type.

All of these functional interfaces have primitive specializations like the IntPredicate,LongPredicate,DoublePredicate which are of the functional interface Predicate<T>.

Example:

IntPreicate evenNumbers = (int i) -> i % 2 == 0; (no boxing)

Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1; (boxing)

Overloading method using IntPredicate and Predicate<T>:

package com.lambda;

import java.util.function.IntPredicate;
import java.util.function.Predicate;

public class LambdaFunctInter {
       
        public static void method1(Predicate<Integer> predicate){
              System. out.println("In Predicate Functional Inteface" );
       }
       
        public static void method1(IntPredicate predicate){
              System. out.println("In Premitive Predicate Functional Interface" );
       }
       
        public static void main(String[] args) {
               method1(i -> true);        
       }
}

Output: This code would return the compile time error message as "The method method1(Predicate<Integer>) is ambiguous for the type LambdaFunctInter".

Here an ambiguity is created as the compiler does not know which method it needs to call and hence throws an error.

Exceptions thrown by Lambdas:

Note that none of the functional interfaces defined in Java 8 throws an checked exception. In case if you want to throw an exception then create your own functional interface or wrap the try catch block in the lambda.

Important concept on finding the Type of functional Interface of lambda expression:

Because of the idea of target typing the same lambda expression can be associated with different functional interfaces if they have compatible abstract method signature.

Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;

Special void compatibility rule:

If the lambda expression has a statement expression as its body, its compatible with a functional interface that returns a void.

Example:

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

Here even though the lambda expression returns a boolean, it is compatible with the Consumer.

But, for the below code this is not compatible.
Predicate<String> p = s -> true;
Consumer<String> b = s -> true; //Compile time error

On the Consumer functional interface this would return an error message as "Void methods cannot return a value".

So from this I could infer that if you are directly returning the return type like "true" or an string "Test", then the void compatibility will not work. If it is called from a method which returns the value then this would work.

Using local variables:

The local variables have to be final or effective final, in case if they need to be used.

Method Reference:

These are like synthetic sugar for lambdas and refer to a single method.

There are four main types of method references:

  1. Static Method: A method reference to an static method can be shown in the below example.
package com.lambda;

import java.util.function.Function;

public class MethodRef {
              
        public static void method1 (Function<Integer,String> function ){
              System. out.println("Inside Method 1" );
       }
       
        public static String method2(Integer i){
               return "Test" ;
       }
       
        public static void main(String[] args) {  
              
               method1(i -> MethodRef.method2(i));//Without method reference
               method1(MethodRef::method2);   //Equivalent method reference code          
       }

  1. Instance method of an arbitrary type:
                    
import java.util.function.BiFunction;
public class MethodRef {      
        public static void method1(BiFunction<MethodRef,Integer,String> function){
              System. out.println("Inside Method 1 BiFunction" );
       }
             
        public String method3(Integer i ){
               return "Test" ;
       }
       public static void main(String[] args) {           
               method1((methodRef,i) -> methodRef.method3( i));
               method1(MethodRef::method3);
       }
}

Note: The number of input parameters needs to match the number of generic type that you are in the functional interface and hence I have used the BiFunction.

  1. Instance method of an existing object:

package com.lambda;
      import java.util.function.Function;

      public class MethodRef {
              
        public static void method1(Function<Integer,String> function){
              System. out.println("Inside Method 1" );
       }

        public static String method2(Integer i ){
               return "Test" ;
       }
     
        public String method3(Integer i ){
               return " Test";
       }      

        public static void main(String[] args) {             
              MethodRef methodRef = new MethodRef();
              method1(i -> methodRef.method3(i ));
              method1(methodRef::method3);

              //Case2
              MethodRef methodRef = new MethodRef();          
              method1(methodRef:: method2); //Throws an error ("The method method2(Integer) from the type MethodRef should be accessed in a static way")

        }
}

Note: In case if you call a static method using the reference type then it would give an error. Saying that static method should be called in a static way as shown in case2.

  1. Constructor references:
The constructor object can be obtained using the Supplier and Function functional interface.

Supplier<Apple> c1 = Apple::new;
Apple a1 =  c1.get();

which is equivalent to:

Supplier<Apple> c1 = ()-> new Apple();
Apple a1 =  c1.get();

If you have a parametrized constructor, then the below should be used.

Function<Integer,Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);

Comparators comparing:

To sorts an list using lambdas we do the following:

For example sorting the list of apples by weight:

Comparator<Apple> comparator = a1.getWeight().compareTo(a2.getWeight());

This can be returned as below:

Comparator<Apple> comparator = comparing(Apple::getWeight);

Below is the internal implementation of the comparing method and this internally calls the Comparator functional interface and returns a Comparator functional interface.


    public static <T, U> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor ,
            Comparator<? super U> keyComparator )
    {
        Objects. requireNonNull(keyExtractor);
        Objects. requireNonNull(keyComparator);
        return (Comparator<T> & Serializable)
            ( c1, c2) -> keyComparator.compare( keyExtractor.apply(c1 ),
                                              keyExtractor.apply(c2 ));
    }