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.
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.