Functions, consumers, suppliers and predicates – let’s go functional!

Read Time:8 Minute, 6 Second

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!


            
            
                    
Happy
Happy
0 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x