Site Search:

Functional interfaces: Supplier, Consumer, Predicate, Function

<Back


Interfaces allow business requirements to be decoupled from classes implementing it. If an interface has only one abstract method, it can be turned into functional interface. The calling code can pass in a lambda expression as the implementation of the functional interface.

The following are java.util.function.* package functional interfaces OCPJP will test, heavily.


Supplier<T>: T get();
Consumer<T>: void accept(T t);
BiConsumer<T>: void accept(T t, U u); 
Predicate<T>: boolean test(T t);
BiPredicate<T, U>: boolean test(T t, U u);
 Function<T, R>: R apply(T t);
 BiFunction<T, U, R>: R apply(T t, U u);
 UnaryOperator<T>: T apply(T t);
 BinaryOperator<T>: T apply(T t1, T t2);


Supplier
Supplier

Supplier functional interface takes no input returns type T. The supplier interface code is:
@FunctionalInterface public interface Supplier<T> {
        public T get();
}

In the following example, we instantiated 2 HashSet and 2 HashMap in functional programming style.

Traditionally you just call Set<String>  set = new HashSet<>(); or HashMap<Integer, String> map = new HashMap<>(); It reads -- I need a set, so I call HashSet's new to instantiate an instance.

In the examples, we code in functional oriented way (while still object oriented). We focus on input and output. Between the two functional coding style in the example, the first one is more functional oriented -- I need some function that takes no (input) parameter, returns (outputs) an object with the same type I specified in the Supplier declaration -- HashSet or HashMap's new function do exactly that, these functions are two of the candidate suppliers that can satisfy my business requirements. I say candidate because I have other choices on the market. If a function provided by a factory class supplies the object better satisfying my business requirement (e.x. filling some defaults),  that function could be a better supplier. How you realize that magic function? It's none of my business, it's your business. I only know you are my supplier, I get() from you for products (and I check before using).

Since Supplier already declared return type, HashSet::new don't have to specify the type, thanks for type erasing:

OCPJP>cat SupplierDemo.java 
import java.util.function.*;
import java.util.*;
public class SupplierDemo {
  public static void main(String...args) {
    Supplier<Set<String>> s1 = HashSet<String>::new;
    Set<String> var = s1.get();
    System.out.println(var);
    s1 = () -> new HashSet<String>();
    System.out.println(s1.get());
    
    Supplier<Map<Integer, String>> s2 = HashMap::new;
    System.out.println(s2.get());
    s2 = () -> new HashMap<>();
    System.out.println(s2.get());
  }
}
OCPJP>javac SupplierDemo.java 
OCPJP>java SupplierDemo
[]
[]
{}
{}
OCPJP>

Consumer
Consumer

Consumer functional interface takes one parameter, returns nothing. Even though it returns nothing, side effects could happen, you may have changed a collection or printed something to the terminal or modified databases with the information carried in by the parameter. The interface code is:
@FunctionalInterface public interface Consumer<T> {
        void accept(T t);
}

In the following code example, we print "printme" twice with different ways. The code reads in functional oriented way -- I need some function that takes one parameter with the type I specified in the Consumer's declaration, but returns nothing. We also added two integers to a TreeSet. Finally, We add a string into a Queue. The last example don't work because calling Supplier's get() method returns a new instance of LinkedList (which implements Queue) each time.

OCPJP>cat ConsumerDemo.java 
import java.util.function.*;
import java.util.*;
public class ConsumerDemo {
  public static void main(String...args) {
    Consumer<String> c1 = System.out::println;
    c1.accept("printme");
    c1 = t -> System.out.println(t);
    c1.accept("printme");
    
    Supplier<Set<Integer>> s1 = TreeSet::new;
    Set<Integer> set = s1.get();
    Consumer<Integer> c2 = set::add;
    c2.accept(10);
    System.out.println(set);
    
    c2 = i -> set.add(i);
    c2.accept(20);
    System.out.println(set);

    Supplier<Queue<String>> s2 = LinkedList::new;
    Queue<String> queue = s2.get();
    Consumer<String> c3 = queue::offer;
    c3.accept("first entry");
    System.out.println(queue);
    
    //what s2.get() give a new instance
    c3 = e -> s2.get().offer(e);
    c3.accept("second entry");
    System.out.println(s2.get());
  }
}
OCPJP>javac ConsumerDemo.java 
OCPJP>java ConsumerDemo
printme
printme
[10]
[10, 20]
[first entry]
[]
OCPJP>

BiConsumer functional interface takes two parameters, returns nothing. The interface code is:

@FunctionalInterface public interface BiConsummer<T, U> {
        void accept(T t, U u);
}

In the following code example, we added two entries into a HashMap in two different ways. The code reads in functional oriented way -- I need some function that takes 2 parameters with the types I specified in the BiConsumer's declaration, but returns nothing.

OCPJP>cat BiConsumerDemo.java 
import java.util.function.*;
import java.util.*;
public class BiConsumerDemo {
  public static void main(String[] args) {
    Supplier<Map<String, String>> s1 = HashMap::new;
    Map<String, String> map = s1.get();
    BiConsumer<String, String> bc1 = map::put;
    bc1.accept("key1", "value1"); 
    System.out.println(map);
    
    bc1 = (k,v) -> map.put(k, v);
    bc1.accept("key2", "value2");
    System.out.println(map);
  }
}
OCPJP>javac BiConsumerDemo.java 
OCPJP>java BiConsumerDemo
{key1=value1}
{key1=value1, key2=value2}
OCPJP>

Predicate functional interface takes one parameter, returns a boolean (not Boolean!).


The interface code:

@FunctionalInterface public interface Predicate<T> {
        boolean test(T t);
}

In the following code example, p1 is a Predicate used to test if a set is empty. It reads: I need a function that takes Set<String> as parameter, returns a boolean. Notice Collection is the interface HashSet implements.

OCPJP>cat PredicateDemo.java 
import java.util.function.*;
import java.util.*;
public class PredicateDemo {
  public static void main(String...args) {
    Supplier<Set<String>> s1 = HashSet::new;
    Set<String> set = s1.get();
    Predicate<Set<String>> p1 = Collection::isEmpty;
    System.out.println(p1.test(set)); 
    
    Consumer<String> c1 = set::add;
    c1.accept("not empty anymore");
    p1 = e -> e.isEmpty();
    System.out.println(p1.test(set)); 
  }  
}
OCPJP>javac PredicateDemo.java 
OCPJP>java PredicateDemo
true
false
OCPJP>

BiPredicate functional interface takes two parameters, returns a boolean.

@FunctionalInterface public interface BiPredicate<T, U> {
        boolean test(T t, U u);
}

The code example uses BiPredicate to match two strings. (This could be a real work scenario,  you could turn a rest call's xml into a stream of Customer objects, then use this BiPredicate to filter on the stream in order to find the customer you are interested in.)

OCPJP>cat BiPredicateDemo.java 
import java.util.function.*;
import java.util.*;
public class BiPredicateDemo {
  public static void main(String args[]) {
    BiPredicate<String, String> bp1 = String::equalsIgnoreCase;
    //String customerHref = database.getCustomer(1000L).getCustomerHref();
    String customerHref = "/xyzcode/customers/1000";
    System.out.println(bp1.test("/xyzcode/customers/1000", customerHref));
    bp1 = (t, u) -> t.equalsIgnoreCase(u);
    System.out.println(bp1.test("/prov/customers/1001", customerHref));
  }
}
OCPJP>javac BiPredicateDemo.java 
OCPJP>java BiPredicateDemo
true
false
OCPJP>

Function Functional takes one parameter, returns an object of the same of different type. This functional interface is the one we knew best -- as the name suggests, a function -- turns input into output. It represents cause effect.
Function
Function


@FunctionalInterface public interface Function<T, R> {
        R apply(T t);
}

In the code example, we use f1 to retrieve the value with a key from a HashMap. s1.get() creates a new empty HashMap, c1.accept("key1", "value1") adds one entry into the HashMap. f1.apply("key1") retrieves the value corresponding to the key string.

OCPJP>cat FunctionDemo.java 
import java.util.function.*;
import java.util.*;
public class FunctionDemo {
  public static void main(String[] args) {
    Supplier<Map<String, String>> s1 = LinkedHashMap::new;
    Map<String, String> map = s1.get();
    BiConsumer<String, String> c1 = map::put;
    c1.accept("key1", "value1");

    Function<String, String> f1 = map::get; 
    System.out.println(f1.apply("key1"));
    f1 = k -> map.get(k);
    System.out.println(f1.apply("key1"));
  }
}
OCPJP>javac FunctionDemo.java 
OCPJP>java FunctionDemo
value1
value1
OCPJP>

BiFunction
BiFunction

BiFunction functional interface takes two parameters, returns a value of the same or different type. Function functional interface takes one parameter, BiFunction functional interface takes two parameters, that's the only difference.

@FunctionalInterface public interface BiFunction<T, U, R> {
        R apply(T t, U u);
}

code example:

OCPJP>cat BiFunctionDemo.java 
import java.util.function.*;
import java.time.*;
public class BiFunctionDemo {
  public static void main(String args []) {
    BiFunction<LocalDate, LocalTime, LocalDateTime> bf1 = LocalDateTime::of;
    System.out.println(bf1.apply(LocalDate.now(), LocalTime.now()));
    
    bf1 = (t, u) -> LocalDateTime.of(t, u);
    System.out.println(bf1.apply(LocalDate.now(), LocalTime.now()));
  }
}
OCPJP>javac BiFunctionDemo.java 
OCPJP>java BiFunctionDemo
2017-02-10T23:26:25.713
2017-02-10T23:26:25.714
OCPJP>

UnaryOperator functional interface is a special case of Function functional interface. They require the parameter and the return object have the same type.

@FunctionalInterface public interface UnaryOperator<T> extends Function<T, T>{}

In the code example, u1 is used to instantiate a new HashSet from an existing HashSet. Notice HashSet::new is assigned to both Supplier and UnaryOperator. Compiler can figure out which overloaded new method should be used from the context. For Supplier, it takes no parameter, returns a value, so public HashSet() is the constructor referenced here. For UnaryOperator, it takes one parameter, returns a value with the same type, so public HashSet(Collections<? extends E> c) is the overloaded constructor referenced there.

OCPJP>cat UnaryOperatorDemo.java 
import java.util.function.*;
import java.util.*;
public class UnaryOperatorDemo {
  public static void main(String [] ar) {
    Supplier<HashSet<String>> s1 = HashSet::new;
    HashSet<String> hset = s1.get();
    Consumer<String> c1 = hset::add;
    c1.accept("dupe me");
    UnaryOperator<HashSet<String>> u1 = HashSet::new; 
    System.out.println(u1.apply(hset));
    c1.accept("dupe me2");
    u1 = t -> new HashSet<String>(t);
    System.out.println(u1.apply(hset));
  }
}
OCPJP>javac UnaryOperatorDemo.java 
OCPJP>java UnaryOperatorDemo
[dupe me]
[dupe me, dupe me2]
OCPJP>

BinaryOperator functional interface is a special case of BiFunction functional interface. It require all the type parameters to be the same type.

@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T, T, T> {}

code example:

OCPJP>cat BinaryOperatorDemo.java 
import java.util.function.*;
public class BinaryOperatorDemo {
  public static void main(String... args) {
    BinaryOperator<String> bo1 = String::concat;
    System.out.println(bo1.apply("add me ", "to her"));
    bo1 = (t1, t2) -> t1.concat(t2);
    System.out.println(bo1.apply("add me ", "to him"));
  }
}
OCPJP>javac BinaryOperatorDemo.java 
OCPJP>java BinaryOperatorDemo
add me to her
add me to him
OCPJP>