Java 8 Functional Interfaces

1. Overview

In this post, we will learn the Java 8 the functional interface with examples.
Key points about the functional interface:
  1. An Interface that contains exactly one abstract method is known as a functional interface.
  2. It can have any number of default, static methods but can contain only one abstract method. It can also declare methods of the object class.
  3. Functional Interface is also known as Single Abstract Method Interfaces or SAM Interfaces. It is a new feature in Java 8, which helps to achieve a functional programming approach.
  4. A functional interface can extend another interface only when it does not have any abstract method.
  5. The Java API has many one-method interfaces such as Runnable, Callable, Comparator, ActionListener, and others. They can be implemented and instantiated using anonymous class syntax.

2. Examples of Custom Functional Interface

Create a custom Sayable interface is a functional interface annotated with @FunctionalInterface.
The @FunctionalInterface annotation indicates that an interface is a functional interface and contains exactly one abstract method.

2.1 Custom Functional Interface Example

Let's create Sayable interface annotated with @FunctionalInterface annotation.
@FunctionalInterface  
interface Sayable{  
    void say(String msg);   // abstract method   
}  
Let's demonstrate a custom functional interface via the main() method.
public class FunctionalInterfacesExample {

    public static void main(String[] args) {

        Sayable sayable = (msg) - > {
            System.out.println(msg);
        };
        sayable.say("Say something ..");
    }
}

3. Java 8 Predefined-Functional Interfaces

Java 8 provides predefined functional interfaces to deal with functional programming by using lambda and method references.

Predefined-Functional Interfaces Examples

Let's discuss  Java Predefined-Functional Interfaces with examples.
Step 1: Create a Person class, this Person class is used to demonstrate Predefined-Functional Interfaces examples.
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.1 Predicate

  1. We need a function for checking a condition. A Predicate is one such function accepting a single argument to evaluate to a boolean result.
  2. It has a single method test which returns the boolean value.
Predicate interface from JDK 8: Let's look at the internal implementation of the Predicate interface. Predicate interface contains exactly one abstract method test(T t). Note that it also contains a default, static methods.
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

   static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
Predicate Example :
public class PredicateExample {
    public static void main(String[] args) {
        Predicate < Person > predicate = (person) - > person.getAge() > 28;
        boolean result = predicate.test(new Person("ramesh", 29));
        System.out.println(result);
    }
}

3.2 Function

It represents a function that accepts one argument and returns a result.
Function interface from JDK 8: Let's look at the internal implementation of Function interface. Function interface contains exactly one abstract method apply(T t). Note that it also contains a default, static methods.
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

   
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
Function Example :
import java.util.function.Function;

public class FunctionExample {

    public static void main(String[] args) {
        // convert centigrade to fahrenheit
        Function < Integer, Double > centigradeToFahrenheitInt = x - > new Double((x * 9 / 5) + 32);

        // String to an integer
        Function < String, Integer > stringToInt = x - > Integer.valueOf(x);
        System.out.println(" String to Int: " + stringToInt.apply("4"));


        Function < PersonEntity, PersonDTO > function = (entity) - > {
            return new PersonDTO(entity.getName(), entity.getAge());
        };
        PersonDTO personDTO = function.apply(new PersonEntity("ramesh", 20));
        System.out.println(personDTO.getName());
        System.out.println(personDTO.getAge());
    }
}

class PersonEntity {
    private String name;
    private int age;

    public PersonEntity(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class PersonDTO {
    private String name;
    private int age;

    public PersonDTO(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.3 Supplier

Represents a supplier of results.
Supplier interface from JDK 8: Let's look at the internal implementation of Supplier interface. Supplier interface contains exactly one abstract method get(T t). Hence we can apply lambda expression to it.
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
Supplier Example :
import java.util.function.Supplier;

public class SuppliersExample {

   public static void main(String[] args) {
  
       Supplier<Person> supplier = () -> { 
           return new Person("Ramesh", 30 );
       };
  
       Person p = supplier.get();
       System.out.println("Person Detail:\n" + p.getName() + ", " + p.getAge());
   }
}

3.4 Consumer

It represents an operation that accepts a single argument and returns no result.
Consumer interface from JDK 8: Let's look at the internal implementation of Consumer interface. Consumer interface contains exactly one abstract method accept(T arg0). Hence we can apply lambda expression to it.
@FunctionalInterface
public interface Consumer<T> {
    void accept(T arg0);

    default Consumer<T> andThen(Consumer<? super T> arg0) {
       Objects.requireNonNull(arg0);
       return (arg1) -> {
       this.accept(arg1);
       arg0.accept(arg1);
    };
  }
}
Consumer Example : 
public class ConsumersExample {

    public static void main(String[] args) {
        List<Person> listOfPerson = new ArrayList<Person>();
        listOfPerson.add(new Person("abc", 27));
        listOfPerson.add(new Person("mno", 26));
        listOfPerson.add(new Person("pqr", 28));
        listOfPerson.add(new Person("xyz", 27));

        listOfPerson.forEach((person) -> {
            System.out.println(" Person name : " + person.getName());
            System.out.println(" Person age : " + person.getAge());
        });
  
  
        // Second example
        Consumer<Person> consumer = (person) -> {
           System.out.println(person.getName());
           System.out.println(person.getAge());
        };
        consumer.accept(new Person("Ramesh", 30));
    }
}
To define lambdas with two arguments, we have to use additional interfaces that contain “Bi” keyword in their names: BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction.
Let’s use a BiFunction implementation that receives a key and an old value to calculate a new value for the salary and return it.

3.5 BiFunction

It represents a function that accepts two arguments and returns a result.
BiFunction interface from JDK 8: Let's look at the internal implementation of BiFunction interface. BiFunction interface contains exactly one abstract method apply(T arg0, U arg1). Hence we can apply lambda expression to it.
@FunctionalInterface
public interface BiFunction<T, U, R> {
 R apply(T arg0, U arg1);

 default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> arg0) {
  Objects.requireNonNull(arg0);
  return (arg1, arg2) -> {
   return arg0.apply(this.apply(arg1, arg2));
  };
 }
}
BiFunction Example :
public class BiFunctionExample {

    public static void main(String[] args) {
  
       BiFunction<Person, Person, Integer> biFunction = (p1,p2) -> {
           return p1.getAge() + p2.getAge();
       };
  
       int totalAge = biFunction.apply(new Person("Ramesh", 10), 
                               new Person("ram", 10));
       System.out.println(totalAge);
  
    }
}

3.6 BiConsumer

It represents an operation that accepts two input arguments and returns no result.
BiConsumer interface from JDK 8 : Let's look at the internal implementation of BiConsumer interface. BiConsumer interface contains exactly one abstract method accpet(T arg0, U arg1). Hence we can apply lambda expression to it.
@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T arg0, U arg1);

    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> arg0) {
        Objects.requireNonNull(arg0);
        return (arg1, arg2) -> {
             this.accept(arg1, arg2);
            arg0.accept(arg1, arg2);
        };
     }
}
BiConsumer Example :

public class BiConsumersExample {

    public static void main(String[] args) {
  
        BiConsumer<Person, Person> biConsumer = (p1, p2) -> {
             System.out.println(" print first persion");
             System.out.println(p1.getName());
             System.out.println(" print second persion");
             System.out.println(p2.getName());
       };
  
       biConsumer.accept(new Person("Ramesh", 10), new Person("ram", 10));
    }
}
You can also define your own custom functional interface. Following is the list of a functional interface which is placed in java.util.function package.

Summary

  • A functional interface is an interface that has exactly one abstract method.
  • Since default methods have an implementation, they are not abstract so a functional interface can have any number of them.
  • If an interface declares an abstract method with the signature of one of the methods of java.lang.Object, it doesn't count toward the functional interface method count.
  • A functional interface is valid when it inherits a method that is equivalent but not identical to another.
  • An empty interface is not considered a functional interface.
  • A functional interface is valid even if the @FunctionalInterface annotation would be omitted.
  • Functional interfaces are the basis of lambda expressions.

The source code of this article is available on  GitHub Repository.

Please write comments, if you want to give any suggestions or feedback about this article would be appreciated.

Comments