Be More Functional: Java's Functional Interfaces
Learn more about popular functional interfaces in Java, including Consumer, Function, and Predicate.
Join the DZone community and get the full member experience.
Join For FreeHey tea lovers, this post is about the functional interfaces provided by Java. We will talk about the basic ones briefly. These functional interfaces are used by the Streams API heavily, so knowing them will make your life easier. Not just streams you can use it anywhere unless you want to. You can find the code on GitHub and the full project here.
Prerequisites
Just make sure you have a solid understanding of functional interfaces and lambdas. This post is dependent on these concepts.
java.util.function
The functional interfaces we will be discussing in your tea break will come from java.util.function
. This package contains Java’s general-purpose functional interface. This helps us to save time by not writing it repeatedly.
It has many functional interfaces (you probably won’t be needing that many in your code), but we are going to see the most common and useful functional interfaces. We will be comparing it to the normal method or function for better comprehension.
Consumer <T>
As the name implies Consumer
consumes inputs. It doesn’t return anything when called. It is similar to a function with the void return type. This functional interface has two methods, void accept(T t)
that triggers, which triggers the interface, and andThen(Consumer<T>)
, which lets you chain multiple Consumers.
You can call multiple Consumers with andThen(consumer1).andThen(consumer2).....
and call the accept(t)
function to terminate the chain. All Consumers in this chaining will use the same input given via accept()
.
//define the consumer which consumes the age to print it
Consumer<Integer> printAgeConsumer = new Consumer<Integer>() {
public void accept(Integer age) {
System.out.println("Age is " + age);
}
};
//call the method
printAgeConsumer.accept(23);// Age is 23
//using lambda
Consumer<Integer> printAgeWithLamda = (age) -> System.out.println("Lamdda : age is " + age);
//will work similar as printAgeConsumer
printAgeWithLamda.accept(223);//Lamda : age is 223
//chaining with andThen(Consumer)
printAgeConsumer //1st
.andThen(printAgeWithLamda)//2nd
.andThen(age -> System.out.println("How old is he ? " + age))//3rd
.accept(23);//this value will be given to each consumer
You may also like: The Best of Java Collections [Tutorials].
Predicate <T>
The Predicate
is used for boolean
values. It tests the given value and returns a boolean by calling test(T t)
. Then, again, like Consumer, you can use chaining withand(anotherPredicate)
. When calling test(T)
in the end, we will complete the chain. Similar to Consumer, they use the same input throughout the chain. It returns true only if all the predicates return true; otherwise, it returns false.
xxxxxxxxxx
Predicate<Integer> isEven = new Predicate<Integer>() {
public boolean test(Integer number) {
return number % 2 == 0;
}
};
boolean is23Even = isEven.test(23);//false
//using lambda
Predicate<Integer> isEvenlambda = (number) -> number % 2 == 0;
boolean is46Even = isEvenlambda.test(46);//true
//chaining with and()
//returns true only of all the predicated returns true else false
isEven // 1st - number should be even
.and(n -> n < 10)//2nd -number should be less than 10
.and(n -> n > 5) // 3rd should be greater than 5
.test(6); // this number will be used in chain
// returns true after completion
Function <T, R>
Yes, that’s the name. Unlike preceding interfaces, Function
has two generic parameters. The first parameter is for input type, and the second one is for the return type. Essentially, Function
is used for transforming a given value into a desired one.
Functions uses apply(T t)
for its functions and andThen(anotherFuntion)
for chaining. But, dissimilar to Consumer
and Predicate
, this chaining does not use the same value to all the Functions
. Instead, they use the result of the preceding Function
. However, keep in mind that the subsequent Function
has the input type equivalent to the current Function’s
return type.
xxxxxxxxxx
Function<Integer, Integer> doubleTheNumber = new Function<Integer, Integer>() {
public Integer apply(Integer number) {
return number * 2;
}
};
int doubleTheNumber2 = doubleTheNumber.apply(2);//4
//using lambda
Function<Integer, Integer> subtract2 = (number) -> number - 2;
int subtract2From4 = doubleTheNumber.apply(4);//2
//chaining with andThen()
//Unlike Predicate and Consumer which uses the same value to all the nodes,
//Function uses the result of previous Function
doubleTheNumber // 1st will double the number
.andThen(subtract2) // 2nd will subtract 2 from doubled number
.andThen(doubleTheNumber) // 3rd subtracted result will be doubled
.apply(4); // (((4 * 2) - 2) * 2) = 12
BiFunction <T, U, V>
BiFunction
is similar to Function
, except this accepts two input parameters as opposed to Function
, which handles only one input. It uses apply(T t, U u)
for triggering. Chaining in this uses Function
instead of BiFuntion
in andThen(Function<V v, E e>)
, since methods don’t have two return values.
xxxxxxxxxx
BiFunction<Integer, Integer, Integer> areaOfRectangle = new BiFunction<Integer, Integer, Integer>() {
public Integer apply(Integer length, Integer breadth) {
return length * breadth;
}
};
areaOfRectangle.apply(2, 2);//4
BiFunction<Integer, Integer, String> areaWitMessage = (lengh, breadth) -> "Area is " + lengh * breadth;
areaWitMessage.apply(4, 3);// Area is 12
//chaining
areaOfRectangle // 1st area will be calulated
.andThen(area -> area * 3) // 2nd calulated area will be multiplied by 3 as height
.apply(2, 3);// 2 * 3 * 3 = 18
Supplier < T >
Until now, we have talked about the interfaces that take input parameters. Supplier
on the other hand, does not demand any arguments; it just produces one by calling get()
, similar to methods that produce values without input, like toString()
or hashCode()
. We can’t use chaining in this since it does not take input parameters.
xxxxxxxxxx
Supplier<Double> randomSupplier = new Supplier<Double>() {
public Double get() {
return Math.random();
}
};
double randomNumber = randomSupplier.get();
System.out.println(randomNumber);
//lambda
Supplier<String> codersTeaUrl = () -> "https://coderstea.com";
System.out.println(codersTeaUrl.get());
Wait, There's More!
There are more Functional interfaces in the package. They are the implementation of these above functional interfaces for specific challenges or usage. We don’t have to understand every functional interface. If you understand the interfaces we discussed, you are good to go. Let's look at them briefly.
The following sample table is from the Java doc. You can find a full table here or here.
Interface | Description |
---|---|
BiConsumer<T,U> | Represents an operation that accepts two input arguments and returns no result. |
BiFunction<T,U,R> | Represents a function that accepts two arguments and produces a result. |
BinaryOperator<T> | Represents an operation upon two operands of the same type, producing a result of the same type as the operands. |
BiPredicate<T,U> | Represents a predicate (boolean-valued function) of two arguments. |
BooleanSupplier | Represents a supplier of boolean -valued results. |
Consumer<T> | Represents an operation that accepts a single input argument and returns no result. |
DoubleBinaryOperator | Represents an operation upon two double -valued operands and producing a double -valued result. |
DoubleConsumer | Represents an operation that accepts a single double -valued argument and returns no result. |
That's it for this post. You can find the code on GitHub here or the full project here.
Further Reading
Published at DZone with permission of Imran Shaikh. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments