Lambda and Functional Interfaces

Programming languages like C++ ,  javascript etc. are called functional programing languages because we can write functions and use them whenever needed. Being Object oriented is not bad but it adds verbosity. Functional Interfaces and Lambda Expression introduced in Java 8 can be used to write clean and crisp code, which is devoid of boiler-plate codes pieces.

Brief Table of Content

  • Introduction to Functional Interfaces
  • Valid Functional Interfaces
  • InValid Functional Interfaces
  • Functinal Interfaces with Lambda
  • Invalid Lambdas
  • Variables in Lambda
  • Lambda as Method Refrences
  • Types of Method refrences
  • Build-in Functional Interfaces
  • Checking Functional Interface types

Introduction to Functional Interfaces

Functional Interfaces are interfaces with exactly one abstract method.They are basis for lambda expressions. Lambda expressions are piece of code that get pass around.


Interface Mammals is a functional interface as it has single abstract method run().

Annotation @FunctionalInterface is just for the sake of clarity, though this is not required.

Compiler will implicitly assume any interface with single abstract method as a functional interface.Marking an interface as a @FunctionalInterface will throw an error at compile time if someone adds more than one abstract method or no method at all.

Further, this annotation will warn developer not to add any other abstract method as this might break code because it will not remain as a functional interface.

Valid Functional Interfaces

Assuming Mammals interface as previously defined , which of the following are valid functional interfaces ?

Answer ? All three of them are valid functional interfaces.

1. First interface Tiger is valid as it adds no new abstract methods.Since it extends interface mammals which in turn has a single abstract method, this interface inherits it, hence a functional interface.

2. Second interface Lion overrides run() method of mammals, therefore this too has single abstract run() method.Hence, Lion is a valid functional interface.

3.Third interface Elephant override run() method of parent Mammals() interface, and has two other methods. One default method and another static method.Java 8, introduced concept of default and static methods neither of which are abstract methods.Hence this too is a valid functional interface.

Invalid Functional Interfaces
















All of above interfaces will compile, and they are valid interfaces, but they are invalid functional interfaces.

1. Interface Animals has no abstract method.
2. Interface Human has two abstract methods. One is inherited from it's parent Mammals.
3. Interface monkey has two abstract methods , hence not a valid functional interface.

Functional Interfaces With Lambda

Lambda expression are piece of code that get pass around, just like anonymous methods.Let's 
start with a simple Comparator functional interface. This interface has an abstract method compare which takes two objects as input parameters and returns an int.












We have a class Max which has a method findMax which finds maximum from a list of intergers.It also accepts instance of Compator as a parameter.

Lambda expression chosen for illustration for this code is quite simple:

(a , b) -> { return a-b ;}

This expression implies Java called method which takes two parameters and returns an int value.
Now you must be having following queries in your mind ?

1. What's the type of  variables 'a' and 'b' ? Integer, String , Object or what ?
2. Which method is actually called ?
3. How did we infer return type of method ?
4. How does this lambda expression a valid one ? What's the syntax of lambda expression ?

Let's answer all above questions.

Understating Lambda Syntax 

In above example we used following lambda expression:

( a, b) -> { return  a-b ; }

Syntax of above lambda expression is like this.
  • We specified two parameters 'a' and 'b' enclosed in parentheses()
  • Arrow parameter separating parameters from body
  • Body which has one line of code(can have more than one) , including braces {}, a semicolon ; and a return statement.
  1. Java infers parameter type from method, which has types defined.
  2. Which method to call is again known to java from context as functional interfaces only carries single abstract methods, there is no way by which conflict might arise.
  3. Return type is again inferred from return type of method.

E.g. Comparator interface used above takes input as Integer(Inferred from Generic type)
Comparator has only one method, compare that java already knows from the definition of functional interface.
Return type is int, which is return type of method compare.


Following expressions are also valid lambda expressions. 

() -> new String("hello,World")
a ->  {return "someString" ;}
(Integer a) -> String.valueOf(a)
(Integer a, String b) -> a + b ;

  • First one is valid lambda expression for a method that takes no parameter and returns a String
  • Second and third are valid lambda expression which takes Integer as an input parameter and returns a String
  • Last one can be used for a functional interface that has an abstract method accepting an Integer and String as input parameters and returns a String
Few other valid lambda expressions:

() -> true                                                            // 0 parameter
a -> {return a. startsWith("test");}                    // 1 parameter
(String a) -> a. startsWith("test")                      // 1 parameter
int x -> {}                                                          // 1 parameter
int y -> {return ;}                                              // 1 parameter
(a , b ) -> b.startswith("test")                            // 2 parameters
(String a, String b) -> a.startsWith("test")        // 2 parameters


Invalid Lambdas

Integer a -> String.valueOf(a)                  //DOES NOT COMPILE 
a, b -> a+b + "";                                        //DOES NOT COMPILE 
Integer a,  String b -> b.substring(0,1)     //DOES NOT COMPILE 

All of them requires Parentheses ()  in parameter ! Parentheses can be skipped only if there is exactly one parameter and it's data type is not specified.

a, b -> a.startsWith("test")                       //DOES NOT COMPILE 
c -> return 10;                                          //DOES NOT COMPILE 
a -> {return  a.startsWith("test")}          //DOES NOT COMPILE 


All above expression are invalid lambda expression. Correct version of all of them are following:

( a, b ) -> a.startsWith("test")            // Parentheses () needed for more than two parameters
c -> { return 10;}                               // {} curly braces needed with return keyword
a -> {return  a.startsWith("test") ;}  //semicolon ; is required after return statement


Data type of parameters are optional. But when data type of one parameter is listed, data type of all parameters must be provided.

(int y, z) -> {int x=1; return y+10; }             //DOES NOT COMPILE 
(String s, z) -> { return s.length()+z; }         //DOES NOT COMPILE 
(a, Animal b, c) -> a.getName()                   //DOES NOT COMPILE 


Re-declaration of a variable is not allowed with lambda expression.We here try to re-declare variable 'a' , which is not allowed. E.g.
(a, b) -> { int a = 0; return 5;}                   //DOES NOT COMPILE 

Declaring another variable is perfectly alright.Here new variable 'c' is declared in body.
(a, b)  -> { int c = 0; return 5;}                   //COMPILES AND A VALID LAMBDA EXPRESSION


Variables in Lambda

Lambda expression can access static variables, instance variables, effectively final method parameters, and effectively final local variables. How many are there in following example ?

1. public interface Human {
2.   String move();
3. }
4. class HomoSapiens{
5. String talk ="talk";
6. void actions(boolean adult) {
7. String takeDecisions= "MyDecision";
8. perform(()-> talk);
9. perform(()-> adult ? "ride car" : "ride bicycle");
10. perform(()-> takeDecisions);  // takeDecisions = "MyNewDecision"
11. }
12. void perform(Human h) {
13. System.out.println(h.move());
14. }
15.    }

Line 8 uses instance variable.
Line 9 uses method parameters.
variable "adult" is effectively final as it is not reassigned anywhere and can be used in lambda expression.
Line 10 uses an effectively final local variable (takeDecisions). If we uncomment assigning it to some other value, reassignment will convert it to non effectively final variable.Compiler will cry that when you use this variable in lambda expression.

Other normal rules of access control remains same. E.g. , lambda can't access private variables in another class. Lambdas can access a subset of variables that are accessible, but never more than that.

Lambdas as Method References

Method reference makes code look crisp by just mentioning the name of method.Let's understand how to use them with examples.

class HumanHelper{
public static int compareByHeight(Human one, Human two) {
return one.getHeight() - two.getHeight();
}
public static int compareByWeight(Human one, Human two) {
return one.getWeight() - two.getWeight();
}
}

We can write following implementation for Comparator interface for sorting two Humans by their height.

Comparator<Human> byHeight= (h1, h2) -> HumanHelper.compareByHeight(h1,h2);

That's Good. But it's size can be reduced further. There is some redundancy that can be removed.

Comparator<Human> byHeight= HumanHelper :: compareByHeight;

The :: operator tells java to pass parameters automatically to method compareByHeight.
Java takes out all other necessary information from context.Compiler knows that this method accepts two parameters of type Human and return an int. Hence, passing all those information are superficial.

HumanHelper :: compareByHeight returns a functional interface, but not int. Operator :: , is used for deferred execution.

Types of Method References

  1. Static methods 
  2. Instance method on a particular interface
  3. Instance method on an instance to be determined at runtime
  4. Constructors
Static Method Reference

Consumer<List<Integer>> methodReference = Collections::sort;

We called a method with one parameter , and Java knows, how to create a lambda with one parameter and pass it to method. Since static sort method has many overloaded methods, Java infers information about correct sort method from context. Here we have declared a Consumer which takes just one parameter of type List<Integer>. Java calls method that matches such description.

Instance Method on of  an Instance

String myStr = "myString";
UnaryOperator<String> instanceRef1= myStr :: concat;
UnaryOperator<String> instanceRef2= s-> s.concat("hello");

We called an instance method , concat on instance of Class String. Concat method takes string as input and concatenates to original string.

Instance method on instance determined at runtime

Predicate methodRef = myStr :: isEmpty;

This looks like a static method on class String, but it's not. Java knows that isEmpty is an instance method that doesn't accept any parameters.Java uses parameter supplied at runtime as instance on which the method is called.

Constructor Reference

Supplier methodRef = ArrayList :: new;

A constructor reference is a special type of method reference that uses new instead of a method, and it creates a new object. 

Build in Functional Interfaces

Common Functional Interface 

---------------------------------------------------------------------------------------------------------------------
Functional Interfaces     # Parameters                 Return Type            Single Abstract Method
---------------------------------------------------------------------------------------------------------------------
Supplier <T>                                  0                                           T                                    get
Consumer <T>                               1(T)                                      void                               accept
BiConsumer <T, U>                       2(T,U)                                  void                               accept
Predicate <T>                                 1(T)                                      boolean                         test
BiPredicate <T, U>                        2(T,U)                                   boolean                         to
Function <T, R>                             1(T)                                       R                                  apply
BiFunction <T, U, R>                     2(T,U)                                   R                                  apply
UnaryOperator <T>                        1(T)                                       T                                  apply
UnaryOperator <T>                        2(T,T)                                    T                                  apply


Several other functional interfaces are defined in the java.util.function package.They are for working with primitives.

Supplier Implementation 

The Supplier interface is defined as :

 @FunctionalInterface 
  public interface Supplier<T>{
        public T get();
 }

Supplier<ArrayList> al = ArrayList:: new;
ArrayList  list= a1.get();
System.out.println(list)

Supplier act as factory of type defined. Here it's ArrayList.You can define supplier of any valid type.
Calling get() method return new instance of ArrayList.

Supplier<ArrayList<String>> s1 = ArrayList<String> :: new;
Supplier<StringBuilder> sb1 = StringBuilder::new;
Supplier<StringBuilder> sb2 =  () -> new StringBuilder();

 Supplier is often used for constructing new objects.

Consumer and BiConsumer Implementation 


The Consumer interface is defined as :

 @FunctionalInterface 
  public interface Consumer<T>{
       void  accept(T t);
 }

@FunctionalInterface 
  public interface BiConsumer<T,U>{
        void T accept(T t, U u);
 }

Consumer is used when you wish to do something with input parameter and not return anything.
BiConsumer does same thing except with two parameters.

Consumer<String> c1= System.out::println;
Consumer<String> c2= a-> System.out.println(a);

c1.accept("Hello, World");
c2.accept("Hello, World");

This will print "Hello, World" twice.Consumer examples uses method reference System.out::println.
Java determined which overloaded println() method to called from context of lambda.

BiConsumer is called with two parameters.Those two parameters can be of different type.
E.g. We can put a key and a value in a map using this interface.

Map<String, Integer> map= new HashMap<>();
BiConsumer<String , Integer> b1= map::put;
BiConsumer<String , Integer> b1= (k,v) -> map.put(k,v);

b1.accept("age", 21);
b2.accept("height", 67 );

System.out.println(map);

Output is {"age"=21, "height"=67}  , Output shows both BiConsumer implementation got called. We have applied instance method reference since we wanted to call method on local variable map. 


Predicate and BiPredicate Implementation

Predicate is used usually when filtering or matching.BiPredicate is just like a Predicate except that it takes two parameters instead of one.


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

@FunctionalInterface 
  public interface BiPredicate<T,U>{
        boolean test(T t, U u);
 }

Predicate is used to test a condition.

Predicate<String> isEmptyString1= String :: isEmpty;
Predicate<String> isEmptyString2=  x ->x.isEmpty() ;

System.out.println(isEmptyString1.test(""));
System.out.println(isEmptyString2.test(""));

This prints true twice.

BiPredicate<String, String> b1 = String :: startsWith;
BiPredicate<String, String> b2 = (str, prefix) -> str.startsWith(prefix);

System.out.println(b1.test("Indian", "India"));
System.out.println(b1.test("Indian", "India"));

This prints true twice.BiPredicate takes two parameters.Above BiPredicate uses method reference and called an instance method.

Function and BiFunction Implementation

Function converts one parameter into another parameter of same or different type. BiFunction , takes two parameters of same or different type and turn it into a type similar to either of input parameters or a completly different one.

@FunctionalInterface 
  public interface Function<T, R>{
     R apply(T t);
 }

@FunctionalInterface 
  public interface BiFunction<T,U,R>{
       R  apply(T t, U u);
 }

E.g. This function converts a String to length of String.

Function<String, Integer> f1= String :: length;
Function<String, Integer> f2= x -> x.length();

System.out.println(f1.apply("India"));
System.out.println(f2.apply("India"));

Both will return 5.

Following will combilne two String objects and produces another String.

BiFunction<String,String, String> b1  = String ::concat;
BiFunction<String,String, String>  b1 = (string, toAdd) -> string.concat(toAdd);


System.out.println(b1.apply("Hello," "World"));
System.out.println(b2.apply("Hello," "World"));

Both will produce "Hello,World".

UnaryOperator and BinaryOperator Implementation

Unary and Binary represents special case of Function.They need to all parameters of same type. Unary operator transform its value into one of same type. UnaryOperarator extends Function.Similarly, BinaryOperator extends BiFunction.

@FunctionalInterface 
  public interface UnaryOperator<T> extends Function<T,T>{
     T apply(T t);
 }

@FunctionalInterface 
  public interface BinaryOperator<T> extends BiFunction<T,T,T>{
       T  apply(T t1, T t2);
 }

E.g. This function converts a String to length of String.

UnaryOperator<String> u1= String :: toUpperCase;
UnaryOperator<String>  u2= x -> x.toUpperCase();

System.out.println(u1.apply("India"));
System.out.println(u2.apply("India"));

Both will return print "INDIA" twice.

Following will combilne two String objects and produces another String. No need to specify return type because UnaryOperaor requires it to be of same type.

BinaryOperator<String> b1  = String ::concat;
BinaryOperator<String>  b1 = (string, toAdd) -> string.concat(toAdd);


System.out.println(b1.apply("Hello," "World"));
System.out.println(b2.apply("Hello," "World"));

Both will produce "Hello,World".

Checking Functional Interface Types

Let's check our knowledge in finding correct functional interface with few exercises.

________________List<String> ex1= x -> "".equals(x.get(0));
________________<Long> ex2 =(Long l) -> System.out.println(l);
________________<String ,String> ex3 = (s1, s2) -> false; 

Example one passes one parameter and returns a boolean. This hint us that it could be either Function or Predicate. Since it takes only one parameter, it's a Predicate.

Example two takes one Long parameter and doesn't return anything. This tell us that it's a Consumer.

Example three takes two parameters of type String and return a boolean;Here boolean is returned with two input parameters, it's a BiPredicate.
Note, if return type would have been Boolean Object , instead of boolean primitive, that this interface would have been a BiFunction as BiFunction has return type Object.

That's all about lambdas and functional interfaces. Functional interface for primitives isn't covered as a part of this post but it's quite similar to Functional interfaces that we have covered here.There will be subtle difference but with knowledge gained in this post, you can quickly learn them.


Happy Learning!!



















Comments