Working with Built‐in Functional Interfaces
java.util.function packageIMPLEMENTING SUPPLIER
A Supplier is used when you want to generate or supply values without taking any input. The Supplier interface is defined as follows:
@FunctionalInterface
public interface Supplier<T> {
T get();
}LocalDate object using the factory method now().LocalDate::now method reference is used to create a Supplier to assign to an intermediate variable s1.Supplier<LocalDate> s1 = LocalDate::now; Supplier<LocalDate> s2 = () -> LocalDate.now(); LocalDate d1 = s1.get(); LocalDate d2 = s2.get(); System.out.println(d1); System.out.println(d2);
Supplier<StringBuilder> s1 = StringBuilder::new; Supplier<StringBuilder> s2 = () -> new StringBuilder(); System.out.println(s1.get()); System.out.println(s2.get());
Supplier<ArrayList<String>> s3 = ArrayList<String>::new; ArrayList<String> a1 = s3.get(); System.out.println(a1);
What would happen if we tried to print out s3 itself?
System.out.println(s3);
The code prints something like this:
functionalinterface.BuiltIns\$\$Lambda$1/0x0000000800066840@4909b8da
IMPLEMENTING CONSUMER AND BICONSUMER
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
// omitted default method
}
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
// omitted default method
}Consumer<String> c1 = System.out::println;
Consumer<String> c2 = x -> System.out.println(x);
c1.accept("Annie");
c2.accept("Annie");This example prints Annie twice.
var map = new HashMap<String, Integer>();
BiConsumer<String, Integer> b1 = map::put;
BiConsumer<String, Integer> b2 = (k, v) -> map.put(k, v);
b1.accept("chicken", 7);
b2.accept("chick", 1);
System.out.println(map);var map = new HashMap<String, String>();
BiConsumer<String, String> b1 = map::put;
BiConsumer<String, String> b2 = (k, v) -> map.put(k, v);
b1.accept("chicken", "Cluck");
b2.accept("chick", "Tweep");
System.out.println(map);IMPLEMENTING PREDICATE AND BIPREDICATE
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// omitted default and static methods
}
@FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
// omitted default methods
}Predicate<String> p1 = String::isEmpty;
Predicate<String> p2 = x -> x.isEmpty();
System.out.println(p1.test("")); // true
System.out.println(p2.test("")); // trueThis prints true twice.
BiPredicate<String, String> b1 = String::startsWith;
BiPredicate<String, String> b2 = (string, prefix) -> string.startsWith(prefix);
System.out.println(b1.test("chicken", "chick")); // true
System.out.println(b2.test("chicken", "chick")); // trueIMPLEMENTING FUNCTION AND BIFUNCTION
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
// omitted default and static methods
}
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
// omitted default method
}Function<String, Integer> f1 = String::length;
Function<String, Integer> f2 = x -> x.length();
System.out.println(f1.apply("cluck")); // 5
System.out.println(f2.apply("cluck")); // 5BiFunction<String, String, String> b1 = String::concat;
BiFunction<String, String, String> b2 = (string, toAdd) -> string.concat(toAdd);
System.out.println(b1.apply("baby ", "chick")); // baby chick
System.out.println(b2.apply("baby ", "chick")); // baby chickCREATING YOUR OWN FUNCTIONAL INTERFACES
3 paramters:
@FunctionalInterface
interface TriFunction<T,U,V,R> {
R apply(T t, U u, V v);
}4 parameters:
@FunctionalInterface
interface QuadFunction<T,U,V,W,R> {
R apply(T t, U u, V v, W w);
}Remember that you can add any functional interfaces you’d like, and Java matches them when you use lambdas or method references.
IMPLEMENTING UNARYOPERATOR AND BINARYOPERATOR
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> { }
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
// omitted static methods
}method signatures look like this:
T apply(T t); // UnaryOperator T apply(T t1, T t2); // BinaryOperator
UnaryOperator<String> u1 = String::toUpperCase;
UnaryOperator<String> u2 = x -> x.toUpperCase();
System.out.println(u1.apply("chirp")); // CHIRP
System.out.println(u2.apply("chirp")); // CHIRPThis prints CHIRP twice.
We don’t need to specify the return type in the generics because UnaryOperator requires it to be the same as the parameter
BinaryOperator<String> b1 = String::concat;
BinaryOperator<String> b2 = (string, toAdd) -> string.concat(toAdd);
System.out.println(b1.apply("baby ", "chick")); // baby chick
System.out.println(b2.apply("baby ", "chick")); // baby chickCHECKING FUNCTIONAL INTERFACES
What functional interface would you use in these three situations?
Supplier<String>Function<String, Boolean>Predicate<String>. Note that a Predicate returns a boolean primitive and not a Boolean object.BinaryOperator<Integer> or a BiFunction<Integer,Integer,Integer>BinaryOperator<Integer> is the better answer of the two since it is more specific.What functional interface would you use to fill in the blank for these?
6: \_\_\_\_\_\_\_<List> ex1 = x -> "".equals(x.get(0)); 7: \_\_\_\_\_\_\_<Long> ex2 = (Long l) -> System.out.println(l); 8: \_\_\_\_\_\_\_<String, String> ex3 = (s1, s2) -> false;
These are meant to be tricky:
6: Function<List<String>> ex1 = x -> x.get(0); // DOES NOT COMPILE 7: UnaryOperator<Long> ex2 = (Long l) -> 3.14; // DOES NOT COMIPLE 8: Predicate ex4 = String::isEmpty; // DOES NOT COMPILE
CONVENIENCE METHODS ON FUNCTIONAL INTERFACES
Several of the common functional interfaces provide a number of helpful default methods.
It’s a bit long to read, and it contains duplication.
What if we decide the letter e should be capitalized in egg?
Predicate<String> egg = s -> s.contains("egg");
Predicate<String> brown = s -> s.contains("brown");
Predicate<String> brownEggs = s -> s.contains("egg") && s.contains("brown");
Predicate<String> otherEggs = s -> s.contains("egg") && ! s.contains("brown");Predicate<String> egg = s -> s.contains("egg");
Predicate<String> brown = s -> s.contains("brown");
Predicate<String> brownEggs = egg.and(brown);
Predicate<String> otherEggs = egg.and(brown.negate());Consumer<String> c1 = x -> System.out.print("1: " + x);
Consumer<String> c2 = x -> System.out.print(",2: " + x);
Consumer<String> combined = c1.andThen(c2);
combined.accept("Annie"); // 1: Annie,2: AnnieFunction<Integer, Integer> before = x -> x + 1; Function<Integer, Integer> after = x -> x * 2; Function<Integer, Integer> combined = after.compose(before); System.out.println(combined.apply(3)); // 8