Simplify Your Java Code With Functional Programming: Quick overview
You may have heard about functional programming, it's a programming paradigm like many others. Functional programming is a style of programming that helps us to implement some tasks in a really easy and robust way.
Lambda Expressions
Lambda expressions were added to Java in 2014 together with Java 8 version. Simply put, a Java lambda expression is a function which can be created without belonging to any class and express an instance of a functional interface.
Functional Interface
Basically, in Java a functional interface is an interface with only a single abstract method. There many functional interfaces in Java World like Runnable, ActionListener, Comparable.
Since Java 8 we have a package called java.util.function which provides some predefined functional interfaces to help us write common tasks.
In this article I'm going to present the following 4 types of functional interfaces:
- Consumer Interface
- Supplier Interface
- Function
- Predicate
Consumer Interface
A consumer interface represents an operation that takes only one argument and doesn't return any value. There are a lot of methods that expect a consumer interface. Below you can see in the official documentation that it has only one abstract method called accept(T t) with no returned value.
Usage
Here is an example of how we can make the use of a consumer interface to iterate over a list of elements and to turn your code into a declarative style.
Imperative style without consumer interface
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
for (Integer n : numbers) {
System.out.println(n);
}
}
Declarative style using consumer interface
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
numbers.forEach(n -> System.out.println(n));
}
The forEach method has a consumer function as a parameter, so we can simply pass an anonymous function as an argument.
Supplier Interface
The supplier interface is the opposite of the consumer interface, since it supplies a value instead of consume a value. Below you can see in the official documentation that it has only one abstract method called get() which returns a value of type T.
Usage
Here is an example of how we can make the use of a supplier interface to generate a random value. The supplier interface also has few specializations for work with primitive values, as you can see below, it's better to use these specializations whether working with primitive values to avoid paying the cost of autoboxing/unboxing.
Using Generic Supplier Interface
public static void main(String[] args) {
Supplier<Double> getRandom = () -> Math.random();
System.out.println(getRandom.get());
}
Using DoubleSupplier Interface
public static void main(String[] args) {
DoubleSupplier getRandom = () -> Math.random();
System.out.println(getRandom.getAsDouble());
}
Function
A Function interface represents a function with just one argument and returns a value. Below you can see in the official documentation that it has only one abstract method called apply(T t) which takes a value of type T as an argument and returns a value of type R. It's also has a method called andThen() that takes a Function as an argument and return a Function as result, using this method we can split our code into small pieces and then combine them together.
Usage
Here is an example of how we can make the use of a Function interface to pass an Integer as an argument and return its half as a Double value, then we combine the result with another Function that takes a Double as an argument and adds 10, then return as Double value.
Using Function Interface combined
public static void main(String[] args) {
Function<Integer, Double> half = number -> number / 2.0;
Function<Double, Double> add = number -> number + 10;
System.out.println(half
.andThen(add)
.apply(10));
}
//output: 15.0
Predicate
A predicate interface represents a function with just one argument and returns a boolean value. Predicate has thousands of applications, for example, we can use it to filter some data. Below you can see in the official documentation that it has only one abstract method called test(T t) which takes an object of type T as an argument and returns a boolean. It also has more two methods called and(Predicate<? super T> other) and or(Predicate<? super T> other) that takes a Predicate as an argument, so we can use it to combine and build more complex predicates.
Usage
Here is an example of how we can make the use of a Predicate Interface to check whether a number is greater than 18 and then check if its lower than 30.
Using Predicate Interface
public static void main(String[] args) {
Predicate<Integer> isGraterThan18 = number -> number > 18;
Predicate<Integer> isLowerThan30 = number -> number < 30;
Predicate<Integer> isGranterThan18AndLowerThan10 = isGraterThan18
.and(isLowerThan30);
System.out.println(isGranterThan18AndLowerThan10
.test(20));
}
//output: true
Conclusion
In this article, I tried to present 4 different functional interfaces in Java 8 that can be used as lambda expressions and brings a lot of flexibility and readability to our code. We can use the power of functional interfaces in different situations in real-world applications.
There are more Functional interfaces in the package java.util.function to you to explore and have fun. Check it out