Functional Interfaces, Lambda Expressions and Method References

Overview

Lets look at an example before we dive into the details.

Reference: https://github.com/explorer436/programming-playground/tree/main/java-playground/functional-interface-lambda-expressions-demo-project

An interface with one abstract method:

public interface A {
    public abstract void show(int i);
}

A class that implements the interface:

public class B implements A{
    @Override
    public void show(int i) {
        System.out.println("In show() - i: " + i);
    }
}

A client class that uses the implementation class B:

public class Driver1 {
    public static void main(String[] args) {
        A obj = new B();
        obj.show(5);
    }
}

What if you don’t want to write Class B? You can use an anonymous class. Anonymous clases are only created for one-time use and we cannot reuse them. There is a lot of boilerplate code in an Anonymous class.

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

        // Using anonymous class
        A obj = new A() {
            @Override
            public void show(int i) {
                System.out.println("In show() - i: " + i);
            }
        };
        obj.show(6);
    }
}

Here comes the lambda expression to reduce boilerplate code and verbosity. Lambda expressions are anonymous functions that don’t have a name and that don’t belong to any class. Lambda expressions provide a clear and concise way to represent an implementation for an interface method via an expression. In Java, Lambda expressions work only if we have a Functional Interface.

public class DriverUsingLambdaExpression {
    public static void main(String[] args) {
        /*A obj = new A() {
            @Override
            public void show() {
                System.out.println("In show()");
            }
        };
        obj.show();*/

        // Remove the boilerplate code
        // The use of new() is boilerplate
        // The name of the method is boilerplate

        // Make sure you use "->". That represents lambda.

        /*A obj = i -> {
            return System.out.println("In show() - i: " + i);
        };*/

        // This can also be written as:
        // A obj = (int i) -> System.out.println("In show() - i: " + i);

        // This can also be written as:
        // A obj = (i) -> System.out.println("In show() - i: " + i);

        obj.show(7);
    }
}

What are functional or SAM interfaces?

Since: 1.8

https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html

Definition

Functional Interfaces are an interface with only one abstract method. Due to which it is also known as the Single Abstract Method (SAM) interface. It is known as a functional interface because it wraps a function as an interface or in other words a function is represented by a single abstract method of the interface.

Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

A functional interface in Java is an interface that consists of just one abstract method (i.e., a method that is not implemented). Although this method must have a return type, it cannot accept arguments. The method must also be public and in an accessible class or interface.

Besides the one abstract method, you can create the following types of methods in a functional interface in Java:

  1. Default methods
  2. Static methods
  3. Methods inherited from the Object class

What was the reason that functional interfaces were introduced into the language?

Benefits of Functional interfaces

The most significant benefit of Functional interfaces is that they make it possible to create abstractions that multiple classes can use without copying and pasting code. This is especially helpful when developers need to create a complex abstraction with various methods and behaviors.

Functional interfaces make it easier to write functional-style code by reducing the verbosity of anonymous inner classes.

Functional interfaces are a great way to add some flexibility to your code. By using a Functional interface, developers can specify exactly what functionality you need from an object, and then have that object be implemented by any class that meets your requirements.

They allow developers to create functions as first-class objects, which opens up new possibilities for creating reusable code and simplifying development processes.

In Java, using Functional interfaces, programmers can pass a function as a parameter instead of a reference object, which reduces the amount of boilerplate code you have to write.

Benefits of Lambda expressions

The introduction of lambda expressions in Java 8 provided a new syntactic improvement over its earlier counterparts and helped eliminate boilerplate code in your applications. Functional interfaces are a first-class citizen of Java and their implementation can be treated as lambda expressions.

Syntax for defining funtional interfaces

@FunctionalInterface: An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.

It’s recommended that all functional interfaces have an informative @FunctionalInterface annotation. This clearly communicates the purpose of the interface, and also allows a compiler to generate an error if the annotated interface does not satisfy the conditions.

For declaring Functional Interfaces @FunctionalInterface annotation is optional to use. However, it helps the compiler. If this annotation is used for interfaces with more than one abstract method, it will generate a compiler error. The compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration. The advantage of using the annotation is, if an interface is annotated with this annotation type, compilers will know that it is supposed to be a functional interface. And if there are any errors, the compilers will catch them at compile time and throw errors.

Any interface with a SAM(Single Abstract Method) is a functional interface, and its implementation may be treated as lambda expressions.

Instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

@FunctionalInterface // Annotation is optional
public interface Foo() {

    // Default Method - Optional can be 0 or more
    public default String HelloWorld() {
        return "Hello World";
    }

    // Static Method - Optional can be 0 or more
    public static String customMessage(String msg) {
        return msg;
    }

    // Single Abstract Method
    public void doSomething();
}

Class implementing this functional interface:

public class FooImplementation implements Foo {

    // Default Method - Optional to Override
    @Override
    public default String helloWorld() {
        return "Hello Java 8";
    }

    // Method Override
    @Override
    public void doSomething() {
        System.out.println(“Hello World”);
    }
}

Working with objects of type ~FooImplementation

public static void main(String[] args) {
    FooImplementation fi = new FooImplementation();
    System.out.println(fi.helloWorld());
    System.out.println(fi.customMessage(“Hi”));
    fi.doSomething();
}

Can a functional interface extend/inherit another interface?

A functional interface cannot extend another interface with abstract methods as it will void the rule of one abstract method per functional interface. e.g:

interface Parent {
    public int parentMethod();
}

@FunctionalInterface // This cannot be FunctionalInterface
interface Child extends Parent {
    public int childMethod();
    // It will also extend the abstract method of the Parent Interface
    // Hence it will have more than one abstract method
    // And will give a compiler error
}

It can extend other interfaces which do not have any abstract method and only have the default, static, another class is overridden, and normal methods. e.g:

interface Parent {
    public void parentMethod(){
        System.out.println("Hello");
    }
}

@FunctionalInterface
interface Child extends Parent {
    public int childMethod();
}

Lambdas in Java 8

  1. Java 8 brought a powerful new syntactic improvement in the form of lambda expressions. A lambda is an anonymous function that we can handle as a first-class language citizen. For instance, we can pass it to or return it from a method.
  2. Before Java 8, we would usually create a class for every case where we needed to encapsulate a single piece of functionality. This implied a lot of unnecessary boilerplate code to define something that served as a primitive function representation.
  3. In functional programming, a piece of code may be considered data. This is where lambda expressions help. You can use lambda expressions to pass code to another function or object.
  4. It should be noted that lambda expressions use a Functional interface as a data type. Because there is just one abstract method in a functional interface, the implementation of that method becomes the code that can be passed as an argument to another method.

Reference: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html http://learnyouahaskell.com/higher-order-functions#lambdas

What is the basic structure/syntax of a lambda expression?

FunctionalInterface fi = (String name) -> {
    System.out.println("Hello "+name);
    return "Hello "+name;
}

Lambda expression can be divided into three distinct parts as below:

  1. List of Arguments/Params:

(String name)

A list of params is passed in () round brackets. It can have zero or more params. Declaring the type of parameter is optional and can be inferred for the context.

  1. Arrow Token:

-> Arrow token is known as the lambda arrow operator. It is used to separate the parameters from the body, or it points the list of arguments to the body.

  1. Expression/Body:
{
    System.out.println("Hello "+name);
    return "Hello "+name;
}

A body can have expressions or statements. {} curly braces are only required when there is more than one line. In one statement, the return type is the same as the return type of the statement. In other cases, the return type is either inferred by the return keyword or void if nothing is returned.

What are the features of a lambda expression?

Below are the two significant features of the methods that are defined as the lambda expressions:

  1. Lambda expressions can be passed as a parameter to another method.
  2. Lambda expressions can be standalone without belonging to any class.

What are the types and common ways to use lambda expressions?

A lambda expression does not have any specific type by itself. A lambda expression receives type once it is assigned to a functional interface. That same lambda expression can be assigned to different functional interface types and can have a different type.

For eg consider the expression s -> s.isEmpty()

Predicate<String> stringPredicate = s -> s.isEmpty();
Predicate<List> listPredicate = s -> s.isEmpty();
Function<String, Boolean> func = s -> s.isEmpty();
Consumer<String> stringConsumer = s -> s.isEmpty();

Common ways to use the expression

  1. Assignment to a functional Interface —> Predicate<String> stringPredicate = s -> s.isEmpty();
  2. Can be passed as a parameter that has a functional type —> stream.filter(s -> s.isEmpty())
  3. Returning it from a function —> return s -> s.isEmpty()
  4. Casting it to a functional type —> (Predicate<String>) s -> s.isEmpty()

How do lambda expressions relate to functional interfaces?

As lambda expressions are similar to anonymous functions, they can only be applied to the single abstract method of Functional Interface. It will infer the return type, type, and several arguments from the signature of the abstract method of functional interface.

Reference: https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

Lambda expressions work with only functional interfaces. Not with interfaces with more than one abstract method in them.

Why? Because, if you have something like this:

A2 obj = (i, j) -> i + j;

if you have more than one abstract methods in the interface, how is the implementation supposed to know which method you are implementing?

Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the functional method for that functional interface, to which the lambda expression’s parameter and return types are matched or adapted. Functional interfaces can provide a target type in multiple contexts, such as assignment context, method invocation, or cast context:

// Assignment context
Predicate<String> p = String::isEmpty;

// Method invocation context
stream.filter(e -> e.getSize() > 10)...

// Cast context
stream.map((ToIntFunction) e -> e.getSize())...

What are some of the best practices when it comes to using lambda expressions and functional interfaces?

Reference: https://www.baeldung.com/java-8-lambda-expressions-tips

In Java 8, what is Method Reference?

Very good documentation here: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it’s often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

Method reference is a compact method of Lambda expression. It is a Java 8 construct used to reference a method (of a functional interface) without having to invoke it.

:: (double colon) is used for describing the method reference. The syntax is class::methodName

Kinds of Method References

There are four kinds of method references:

Kind Syntax Examples
Reference to a static method ContainingClass::staticMethodName Person::compareByAge, MethodReferencesExamples::appendStrings
Reference to an instance method of a particular object containingObject::instanceMethodName myComparisonProvider::compareByName, myApp::appendStrings2
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName String::compareToIgnoreCase, String::concat
Reference to a constructor ClassName::new HashSet::new

The following example, MethodReferencesExamples, contains examples of the first three types of method references:

import java.util.function.BiFunction;

public class MethodReferencesExamples {

    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
        return merger.apply(a, b);
    }

    public static String appendStrings(String a, String b) {
        return a + b;
    }

    public String appendStrings2(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {

        // Calling the method mergeThings with a lambda expression
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", (a, b) -> a + b));

        // Reference to a static method
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        // Reference to an instance method of a particular object
        MethodReferencesExamples myApp = new MethodReferencesExamples();
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", myApp::appendStrings2));

        // Reference to an instance method of an arbitrary object of a particular type
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", String::concat));
    }
}

All the System.out.println() statements print the same thing: Hello World!

BiFunction is one of many functional interfaces in the java.util.function package. The BiFunction functional interface can represent a lambda expression or method reference that accepts two arguments and produces a result.

Example of method reference - what does the String::ValueOf expression mean?

It is a static method reference to method Valueof() of class String. It will return the string representation of the argument passed.

Tags

Functional Interfaces - Java pre-defined functional interfaces


Links to this note