Java Lambda – Functional Interfaces

8 min read

In the previous post, we have discussed basics of lambda, with some simple code snippets, we learned the syntax and basic usage of lambdas. In this post we will be learning more advanced topic such as Functional Interfaces and method references.

What is Functional Interface?

Any interface which has exactly one abstract method is a candidate for functional interface. Only these kind of interface can be realised as type of Lambda. It can have any no of methods that may have concrete implementation but an interface must have only single abstract method. They are also known as SAM(Single abstract method) interfaces.

public class FunctionalInterfaceImpl {
    public static void main(String[] args) {

        Thread myRunnableThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Running thread with Runnable");
            }
        });
        
        Thread myLambdaThread = new Thread(() -> System.out.println("Running thread with Lambda"));

        myLambdaThread.run();
        myRunnableThread.run();
    }

}

Above Code demonstrates a functional interface realised as Lambda type. In this example, Runnable is the functional interface as it has exactly one abstract method, i.e run(). Hence, its implementation can be deduced by lambda function.

What is @FunctionalInterface?

Suppose we have a custom interface with only one abstract method and that interface is consumed by many classes. By mistake anyone added one more abstract method, this will inturn break all the lambda types implementing that interface. Therefore, in order to enforce compile time checks this annotation is used. Although, its an optional annotation, but it helps to advice other developers how to use this interface.

@FunctionaInterface
public interface CustomInterface{
   public void customMethod();
   public void anotherMethod(): //error
}

Further we will be learning few inbuilt types of Functional interfaces.

Predicate :- This interface is present in java.util.function.Predicate package. It takes only a single argument and return boolean values based on some condition. condition is specified in the implementation of this interface.

public interface Predicate {  // sample predicate
    boolean test(T t);
}

Predicate interface has more default and static methods which has concrete implementation but only one abstract method test().

public class PredicateFunctionalInterface {
    static Predicate<Integer> p = (integer) -> integer%2==0;
    static Predicate<Integer> p1 = (integer) -> integer%2==0;

    public static void predicateOr(){

        System.out.println("Example of predicate OR: " +p.or(p1).test(10)); //predicate chaining
    }
    public static void predicateAnd(){

        System.out.println("Example of predicate AND: " +p.and(p1).test(9));
    }
    public static void main(String[] args) {


        System.out.println(p.test(4));
        predicateAnd();
        predicateOr();
    }
}
<樂威壯 !– /wp:code –>

Predicate can be implemented as a lambda type. In the above example, we have demonstrated and & or methods of Predicate with test method accepting inputs for the same.

Supplier A supplier is a functional interface which returns a value of some sorts.

Supplier supplier = () -> new Integer((int) (Math.random() * 1000D));

This Java Supplier implementation returns a new Integer instance with a random value between 0 and 1000.

Consumer A consumer is a functional interface which has an abstract function which consumes a value but never returns a value.

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> c1 = (s) -> System.out.println(s.toUpperCase());
        c1.accept("Java8");
    }
}

What is consumer chaining?

Consumer chaining means combining consumers to get desired result using inbuilt default method andThen(). Below is an example.

public class ConsumerChainingExample {

    public static void main(String[] args) {
        Consumer<Integer> c1 = (integer) -> System.out.println("Value of integer is: "+integer);
        Consumer<Integer> c2 = (integer) -> System.out.println("Value of square is: " +integer*integer);

        List<Integer> integerList = Arrays.asList(1,2,3,4);
        integerList.forEach(c1.andThen(c2));
    }
}

Value of integer is: 1
Value of square is: 1
Value of integer is: 2
Value of square is: 4
Value of integer is: 3
Value of square is: 9
Value of integer is: 4
Value of square is: 16

Biconsumer

This functional interface accept two values as arguments rather one and does not return any output.

public class BiconsumerInterfaceExample {
    public static void main(String[] args) {
        BiConsumer<Integer, Integer> multiply = (x,y) ->
                System.out.println("Multiplication of two numbers is: "+x*y);
        BiConsumer<Integer, Integer> divide = (x,y) ->
                System.out.println("Multiplication of two numbers is: "+x/y);
        multiply.andThen(divide).accept(10,5);
    }
}
//Multiplication of two numbers is: 6

Function

This is type of functional interface where we can consume only one input, process and send optional output just like a normal function. Below is the example.

public class FunctionInterfaceExample {
    static Function<String, String> function = (name) -> name.toUpperCase();
    static Function<String, String> addSomeString = (name) -> name.toUpperCase().concat("default");

    public static void main(String[] args) {
        System.out.println("Result is : " +function.apply("JAVA8"));
        System.out.println("Result of and then is : " +function.andThen(addSomeString).apply("JAVA8"));
        System.out.println("Result of compose is : " +function.compose(addSomeString).apply("JAVA8"));
    }
}
//Result is : JAVA8
//Result of and then is : JAVA8default
//Result of compose is : JAVA8DEFAULT

In next blog we will be covering method references and local variables in lambda.

Happy Learning!!