Java 8 has introduced a new feature called Java functional interfaces and lambda expressions. This is a two-part article in which we will be taking a look at these new features. In part 1 of this article, we will be understanding more about functional interfaces.

  1. What are functional Interfaces
  2. Why were functional interfaces added?
  3. In-built Functional Interfaces

Java 8 Interfaces

What is Functional Interface in Java

A functional interface is an interface that has one and only one abstract method. It can have multiple static and default methods. Functional interfaces were added by Java 8 to add functional programming support to Java.

Sample Code

@FunctionalInterface
public interface Printable {
  public void print(String str);
  default void format(String str) {
  System.out.println("Formatting String str ....");
 }
}
  • This code defines an interface called Printable
  • Line 3 specifies an abstract method called print
  • Line 4 specifies a default method called format
  • Since Printable has only one abstract method print, it is a functional interface
  • Line 1 specifies the @FunctionalInterface annotation on the interface. This annotation provides a compile-time check. So, if you add another abstract method within this interface, the compiler flags this by causing an error. This annotation is optional. Even if this annotation is not specified, this interface will still be a functional interface since it has only one abstract method

Why Functional Interface Introduced in Java 8?

Functional interfaces were added to support lambda expressions. Functional interfaces and lambda expressions help to make Java code shorter.

Before Java 8

Before Java 8, in order to implement an interface, we had to create a class that implements the interface and provides code for the abstract methods in the interface.

Sample Code

public class TextPrinter implements Printable{
 public void print(String str) {
  System.out.println("Printing "+str);
}

 public static void main(String[] args) {
  TextPrinter textPrinter = new TextPrinter();
  textPrinter.print("Hello");
 }
}
  • This code defines a class called TextPrinter which implements the Printable interface
  • Line 2 provides an implementation for the print method
  • Line 6 specifies the main method that creates a TextPrinter object called textPrinter and invokes the print method on textPrinter
  • So, you can see that in order to provide code for the print method in the Printable interface, we had to create the TextPrinter class, implement the Printable interface and provide an implementation for the print method. So, it requires a lot of code to implement a single method

Output

Printing Hello

After Java 8

The main advantage of functional interfaces is that they can be implemented inline via a lambda expression. So, this does away with the need to create a class that implements the interface.

Sample Code

public class TextPrinter2 {
 public static void main(String[] args) {
  Printable printable = str -> System.out.println("Printing "+str);
  printable.print("Hello");
 }
}
  • This code defines a class called TextPrinter2. It no longer implements the Printable interface and no longer provides an implementation for the print method
  • Line 3 defines a Printable instance called printable and assigns it the specified lambda expression. (Lambda expressions will be covered in detail in part 2 of this article)
  • Line 4 invokes the print method. This causes the code within the lambda expression to be executed.
  • So, you can see that you no longer need to write elaborate code in order to implement a functional interface method. Compared to the earlier code, this code is much simpler and easier to read.

This code produces the same output as before

In-built Functional Interfaces

There are also some in-built Java 8 functional interfaces provided that can be used in common programming scenarios. These are present in a package called java.util.Function. All the in-built functional interfaces are generic, that is the data type of their arguments/return values can be specified via generics. Let us take a look at some of the in-built functional interfaces

Consumer

The Consumer interface can be used to operate on an input argument. Its abstract method accept has the following signature:

void accept(T t);

Sample Code

public class ConsumerDemo {
 public static void main(String[] args) {
  Consumer<Integer> multiplier = num -> System.out.println(num*num);
  multiplier.accept(10);
  multiplier.accept(4);
 }
}
  • This code defines a class called ConsumerDemo.
  • The main method defines a Consumer instance called multiplier of Integer type. So multiplier operates on an Integer parameter.
  • The accept method is implemented via a lambda expression that simply multiplies the input number by itself and prints the result
  • Lines 4,5 invoke the multiplier accept method with different Integer values

Output

100
16

Supplier

The Supplier interface can be used to produce a value. Its abstract method get has the following signature:

T get();

Sample Code

public class SupplierDemo {
 public static void main(String[] args) {
  Supplier<Double> randomNumberSupplier = () -> new Random(10).nextDouble();
  System.out.println(randomNumberSupplier.get());
  System.out.println(randomNumberSupplier.get());
 }
}
  • This code defines a class called SupplierDemo.
  • The main method defines a Supplier instance called randomNumberSupplier of Double type. So randomNumberSupplier returns a result of type Double.
  • The get method is implemented via a lambda expression. Since it does not accept any parameters, empty parentheses are specified. The body of the lambda expression simply returns a new Random Double number.
  • Lines 4,5 invoke the randomNumberSupplier get method

Output

0.7304302967434272
0.7304302967434272

Since a random number is generated, the output will be different each time.

Function

The Function interface can be used to transform its argument. Its abstract method apply has the following signature:

R apply(T t);

Sample Code

public class FunctionDemo {
 public static void main(String[] args) { 
  Function<LocalDate,Integer> yearRetriever = date -> date.getYear();
  LocalDate today = LocalDate.now();
  System.out.println("Year corresponding to "+today+" is "+yearRetriever.apply(today));
 }
}
  • This code defines a class called FunctionDemo.
  • The main method defines a Function instance called yearRetriever. It accepts an argument of type LocalDate and returns a result of type Integer. LocalDate is a new class added by Java 8 that can be used to represent the current date.
  • The apply method is implemented via a lambda expression that accepts a LocalDate object and returns the year component corresponding to the LocalDate.
  • Line 4 creates a new LocalDate object called today corresponding to the current date and Line 5 invokes the yearRetriever apply method with today

Output

Year corresponding to 2020-12-18 is 2020

Predicate

The Predicate interface can be used to test a condition. Its abstract method test has the following signature:

boolean test(T t);

Sample Code

public class PredicateDemo {
 public static void main(String[] args) {
  Predicate<String> stringChecker = str -> str.isEmpty();
  String s = "Hello";
  boolean result = stringChecker.test(s);
  System.out.println(s+" is empty:"+result);
 }
}
  • This code defines a class called PredicateDemo.
  • The main method defines a Predicate instance called stringChecker of String type. So stringChecker accepts an argument of type String.
  • The test method is implemented via a lambda expression that invokes the isEmpty method on the input String and a boolean value accordingly.
  • Line 4 creates a new String object called s with the value “Hello” and Line 5 invokes the test method with this value.

Output

Hello is empty:false

Conclusion

So in this article, we understood what functional interfaces are and how functional interfaces and lambda expressions can be used to make Java code shorter and easier to read. We also took a look at some of Java’s in-built functional interfaces like Consumer, Supplier, Function, and Predicate. In Part 2 of this article, we will be understanding more about lambda expressions.