As we’ve had our first glance on the functional-like programming features introduced in Java 8, we may take a look at some interfaces which are the part of java.util.function library – a standard JDK8+ library.
-
Consumer interface
The functional interface has one abstract method void accept(T t)
. It consumes one generic argument (we can put there any type of object) and returns nothing (void type). The most commonly known example is default void forEach(Consumer<? super T> action) – method within Iterable interface:
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
That method passes each element of the iterable collection to the consumer given in the argument – in other words – it iterates through the collection invoking the passed method using each element as its argument. The Iterable interface is implemented by all linear collections. Look at the example:
// we create list of some strings List<String> theList = Arrays.asList("hello", "these", "are", "the", "list", "elements"); // then we create an empty ArrayList of strings List<String> secondList = new ArrayList<>(); // we populate the empty list with elements from the first one adding a word theList.forEach(el -> secondList.add("element: " + el)); // we print the second list to the screen using forEach method with static method reference as the paramater secondList.forEach(System.out::println);
Output:
element: hello element: these element: are element: the element: list element: elements
The System.out.println aligns with the accept(T t)
method – it takes one parameter and returns nothing – consumes it.
-
Supplier interface
The java.util.function.Supplier functional interface is pretty simple. It has only one abstract method T get()
– it takes no arguments and returns generic type value. We have 4 other types of that interface which are more “specialized” and return certain types of values, these are: IntSupplier
, DoubleSupplier
, LongSupplier
, BooleanSupplier
– each of them has only one abstract method which return as follows int
, double
, long
or boolean
values as their names suggest.
Example with Supplier interface usage:
package app; import java.util.function.Supplier; public class SupplierApp { public static void main(String[] args) { //we assign Math::random static method to the theMethod field of type Supplier Supplier theMethod = Math::random; // we pass theMethod variable (which points to Math::random method) to giveMeDoubleValue which calls the get() method double resultDouble = giveMeDoubleValue(theMethod); System.out.println("here's some random double value: " + resultDouble); } private static double giveMeDoubleValue(Supplier method) { return (double) method.get(); // here we cast the result to double as get() method returns generic type by default } }
Output:
Here's some random double value: 0.7501269904622642
-
Predicate interface
The basic and most common application of java.util.function.Predicate functional interface is stream filtering. For instance, having a stream of elements, the method Stream<T> filter(Predicate<? super T> predicate)
from interface java.util.stream.Stream takes Predicate type object and return only those elements which meet the predicate conditions. You can read more about streams in Java in my other Java (core) articles.
Predicate interface has only one abstract method boolean test(T t)
– it takes one argument and returns boolean value – true
or false
. It contains also other, static and default methods:
static Predicate isEqual(Object targetRef)
– returns a predicate that checks if two arguments are equal
default Predicate and(Predicate other)
– allows us to chain predicates, it behaves like logical AND gateway
default Predicate negate()
– negates the predicate on which we call the method (logical NOT gateway)
default Predicate or(Predicate other)
– allows us to chain predicates, it behaves like logical OR gateway
Examples:
package app; import java.util.function.Predicate; public class PredicateApp { public static void main(String[] args) { // we define the predicate Predicate<Integer> theScope = i -> i > 10 && i < 42; // here is how to negate the predicate Predicate<Integer> theNegativeScope = theScope.negate(); int theNumber = 33; int otherNumber = 50; //we invoke the boolean test(T t) method of Predicate interface to check numbers against the predicate System.out.printf("The number %d is within the scope: " + theScope.test(theNumber) + "\n", theNumber); System.out.printf("The number %d is within the scope: " + theScope.test(otherNumber) + "\n", otherNumber); //we get opposite results using negated predicate System.out.printf("The number %d is not within the scope: " + theNegativeScope.test(theNumber) + "\n", theNumber); System.out.printf("The number %d is not within the scope: " + theNegativeScope.test(otherNumber), otherNumber); } }
Output:
The number 33 is within the scope: true The number 50 is within the scope: false The number 33 is not within the scope: false The number 50 is not within the scope: true
Here’s how to use predicate on stream filtering:
package app; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class PredicateApp { public static void main(String[] args) { // let's create a list of animals and pets List<String> animalsAndPets = Arrays.asList("Cat", "Moth", "Cougar", "Elephant", "Giraffe", "Tiger", "Dog", "Opossum"); List<String> filteredList = getFilteredNames(3, animalsAndPets); // here we print out the filtered list to the standard output filteredList.forEach(System.out::println); } private static List<String> getFilteredNames(int length, List<String> names) { return names.stream() .filter(name -> name.length() > length) //returns names that are longer than the value passed in length variable .collect(Collectors.toList()); } }
Output:
Cougar Elephant Giraffe Tiger Opossum
-
Function interface
The functional interface contains one abstract method R apply(T t)
which transforms which takes generic type argument T and transofms it to other type R. It has also other methods:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
– dd
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
– dd
static <T> Function<T, T> identity()
– dd
It behaves like a mathematical function y = x – it takes one parameter (like x) and converts it to other one (giving us y).
Examples:
package app; import java.util.function.Function; public class FunctionApp { public static void main(String[] args) { //declaring predicates and assigning them to Function type references Function<Double, Double> doubleTheValue = x -> x * 2; Function<Double, Double> quater = x -> x / 4; double number = 5.8; Double result = doubleTheValue.apply(number); //applying doubleTheValue on the given number System.out.printf("Doubling the value of %.2f: %.2f\n", number, result); //composing doubleTheValue function with quater function Function<Double, Double> doubleAndQuater = doubleTheValue.compose(quater); number = 10.9; result = doubleAndQuater.apply(number); System.out.printf("Doubling the value of %.2f and dividing it by 4: %.2f", number, result); } }
Output:
Doubling the value of 5.80: 11.60 Doubling the value of 10.90 and dividing it by 4: 5.45
Just like it was in case of Supplier interface, the Function interface has more specialized variants, the list can be found here (it’s quite long :)): https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
Example of using BiFunction interface:
package app; import java.util.Random; import java.util.function.BiFunction; public class FunctionApp { public static void main(String[] args) { //we declare our BiFunciton here - it takes two Integers and returns a String - we can put there any type we want BiFunction<Integer, Integer, String> biFunctionExample = (x, y) -> { final int comparisonResult = x.compareTo(y); if (comparisonResult == 0) { return "The numbers are even!"; } else if (comparisonResult > 0) { return "First number " + x + " is greater than " + y; } else { return "Second number " + y + " is greater than " + x; } }; //we use class Random to randomly choose the numbers for tests Random rnd = new Random(); //testing the function by passing there randomly generated Integers, the number within nextInt method is the boundary for number generator for (int i = 0; i < 12; i++) { int firstNumber = rnd.nextInt(999); int secondNumber = rnd.nextInt(999); System.out.println("First generated number is: " + firstNumber); System.out.println("Second generated number is: " + secondNumber + ". Comparison result:"); System.out.println(biFunctionExample.apply(firstNumber, secondNumber) + "\n==========="); } } }
Output:
First generated number is: 822 Second generated number is: 974. Comparison result: Second number 974 is greater than 822 =========== First generated number is: 465 Second generated number is: 786. Comparison result: Second number 786 is greater than 465 =========== First generated number is: 119 Second generated number is: 562. Comparison result: Second number 562 is greater than 119 =========== First generated number is: 568 Second generated number is: 543. Comparison result: First number 568 is greater than 543 =========== First generated number is: 754 Second generated number is: 253. Comparison result: First number 754 is greater than 253 =========== First generated number is: 543 Second generated number is: 842. Comparison result: Second number 842 is greater than 543 =========== First generated number is: 710 Second generated number is: 829. Comparison result: Second number 829 is greater than 710 =========== First generated number is: 957 Second generated number is: 624. Comparison result: First number 957 is greater than 624 =========== First generated number is: 428 Second generated number is: 616. Comparison result: Second number 616 is greater than 428 =========== First generated number is: 208 Second generated number is: 258. Comparison result: Second number 258 is greater than 208 =========== First generated number is: 726 Second generated number is: 781. Comparison result: Second number 781 is greater than 726 =========== First generated number is: 604 Second generated number is: 901. Comparison result: Second number 901 is greater than 604 ===========
That would be it when it comes to the basics – you have had a first glance on java.util.function package functionalities. See you at my other articles!