Functional Interfaces


Functional Interfaces

In this tutorial, we are going to learn about what are functional interfaces and @FunctionalInterface Annotation in Java. Along with this, we will cover Lambda expressions in Java with examples.

So, let’s start with Java Functional Interfaces.

Functional Interfaces
What are Functional interfaces?

If an interface contains only one abstract method, such types of interfaces are called functional interfaces and the method is called functional method or single abstract method (SAM). 

E.g

1. Runnable: It contains only the run() method
2. Comparable: It contains only the compareTo() method
3. ActionListener: It contains only actionPerformed()
4. Callable: It contains only the call() method

Inside the functional interface in addition to a single Abstract method (SAM), we write any number of default and static methods.

E.g

interface Interf { 
   public abstract void m1(); 
   
   default void m2() { 
      System.out.println("Hi this is Ashok..!!"); 
   } 
}

In Java 8, Sun Micro System introduced @FunctionalInterface annotation to specify that the interface is Functional Interface.

@FunctionalInterface
Interface Interf { 
   public void m1();
}

Inside Functional Interfaces, we can take only one abstract method, if we take more than one abstract method then the compiler raises an error message that is called we will get a compilation error.

@FunctionalInterface
interface Interf { 
   public void m1(); 
   public void m2();
}

This code gives a compilation error.

Inside Functional Interfaces, we have to take exactly only one abstract method. If we are not declaring that abstract method then the compiler gives an error message.

@FunctionalInterface 
interface Interf { 

}

This code gives a compilation error.

Functional Interface with respect to Inheritance

If an interface extends Functional Interface and the child interface doesn’t contain any abstract method then the child interface is also Functional Interface.

@FunctionaInterface
interface A {
   public void m1();
}

@FunctionalInterface
interface B extends A { 
}

In the child interface, we can define exactly the same parent interface abstract method.

@FunctionaInterface
interface A {
   public void m1();
}

@FunctionalInterface
interface B extends A { 
   public void m1();
}

In the child interface we can’t define any new abstract methods otherwise child interface won’t be a Functional Interface and if we are trying to use @Functional Interface annotation then the compiler gives an error message.

@FunctionaInterface
interface A {
   public void m1();
}

@FunctionalInterface
interface B extends A { 
   public void m2();
}

This code gives a compilation error.

@FunctionaInterface
interface A {
   public void m1();
}

interface B extends A { 
   public void m2();
}

This is a normal interface so that code compiles without error.

In the above example in both the parent & child interface, we can write any number of default methods and there are no restrictions. Restrictions are applicable only for abstract methods.

Functional Interfaces Vs Lambda Expressions 

Once we write Lambda expressions to invoke its functionality, then Functional Interfaces are required. We can use Functional Interface reference to refer to Lambda Expression. Where ever the Functional Interfaces concept is applicable there we can use Lambda Expressions.

Without Lambda Expression

interface Interf {
   public void m1();
}

public class Demo implements Interface {
   public void m1() {
      System.out.println(“method m1 execution”); 
   }
}

public class Test {
   public static void main(String[] args) { 
      Interf i = new Demo(); 
      i.m1(); 
   }
}

Above code With Lambda expression

interface Interf { 
   public void m1();
}   
class Test { 
   public static void main(String[] args) { 
      Interf i = () -> System.out.println(“Method m1 Execution”); 
      i.m1();
   }
}

Another example

Without Lambda Expression

interface Interf { 
   public void sum(int a,int b); 
}
class Demo implements Interf {
   public void sum(int a,int b) { 
      System.out.println(“The sum:”+(a+b)); 
   } 
}
public class Test {
   public static void main(String[] args) { 
      Interf i = new Demo(); 
      i.sum(20,5); 
   }
}

With Lambda Expression

interface Interf { 
   public void sum(int a,int b);
}   
class Test { 
   public static void main(String[] args) { 
      Interf i = (a, b) -> System.out.println(“The sum:”+(a+b)); 
      i.sum(10,20);
   }
}

Anonymous inner classes vs Lambda Expressions

Wherever we are using anonymous inner classes there may be a chance of using Lambda expression to reduce the length of the code and to resolve complexity.

E.g

With anonymous inner class

class Test { 
   public static void main(String[] args) { 
      Thread t = new Thread(new Runnable() { 
         public void run() { 
           for(int i=0; i<10; i++) { 
              System.out.println("Child Thread"); 
           } 
         }
      });
      t.start();
      for(int i=0; i<10; i++)
         System.out.println("Main thread"); 
   }
}

With Lambda expression

class Test { 
   public static void main(String[] args) { 
      Thread t = new Thread(() -> { 
         for(int i=0; i<10; i++) { 
            System.out.println("Child Thread"); 
         } 
      });
      t.start();
      for(int i=0; i<10; i++)
         System.out.println("Main thread"); 
   }
}
Advantages of Lambda expression
  1. We can reduce the length of the code so that the readability of the code will be improved.
  2. Using the Lambada expression, we can resolve the complexity of anonymous inner classes.
  3. We can provide Lambda expression in the place of an object.
  4. We can pass a lambda expression as an argument to methods.

Note

1. Anonymous inner class can extend concrete class, can extend abstract class, can implement the interface with any number of methods but

2. Lambda expression can implement an interface with only a single abstract method (Functional Interface).

3. Hence if the anonymous inner class implements Functional Interface in that particular case only we can replace it with lambda expressions. Hence wherever an anonymous inner class concept is there, it may not be possible to replace it with Lambda expressions.

4. Anonymous inner class! = Lambda Expression

5. Inside the anonymous inner class we can declare instance variables.

6. Inside the anonymous inner class “this” always refers current inner class object(anonymous inner class) but is not a related outer class object

Example

1. Inside the lambda expression we can’t declare instance variables.

2. Whatever the variables declared inside the lambda expression simply act as local variables

3. Within lambda expression ‘this” keyword represents the current outer class object reference (that is a current enclosing class reference in which we declare lambda expression).

interface Interf { 
   public void m1(); 
}

class Test {
   int x = 777;
   public void m2() { 
      Interf i = () -> { 
         int x = 888;
         System.out.println(x); 888 
         System.out.println(this.x); 777 
      }; 
      i.m1(); 
   } 
   public static void main(String[] args) { 
      Test t = new Test(); 
      t.m2();
   }
}
  • From lambda expression, we can access enclosing class variables and enclosing method variables directly.
  • The local variables referenced from lambda expression are implicitly final and hence we can’t perform re-assignment for those local variables otherwise we get compile time error
interface Interf { 
   public void m1(); 
}

class Test {
   int x = 50;
   public void m2() { 
      Interf i = () -> { 
         int y = 100;
         System.out.println(x); 50
         System.out.println(y); 100
         x = 200;
         y = 300; // C.E
      }; 
      i.m1(); 
   } 
   public static void main(String[] args) { 
      Test t = new Test(); 
      t.m2();
   }
}

Differences between anonymous inner classes and Lambda expression

Capture 44
Functional Interfaces
Scroll to top