Skip to content

Functional Interfaces 2

Hu JiaJun edited this page Nov 29, 2020 · 1 revision

Definition

An interface with single (one and only) abstract method. @FunctionalInterface is generally used to indicate that the interface is a functional interface. Functional interface is the basis for Java to support functional programming.

Meaning

JAVA is object-oriented, but for the convenience of writing, it needs to "pass" a method to a method, However, it is not possible to "pass" a method. So, functional interface just "pass" the object of the implementation class of the interface with single (one and only) abstract method. In this way, a similar transfer method is achieved. In fact, Lambda is an object.

Prerequisite

Functional Interface List

Functional Interface Method Parameter Type Return Type Description
Consumer<T> void accept(T t) T void Accepts 1 parameter, no return
Function<T, R> R apply(T t) T R Accept 1 parameter, return 1 result
Predicate<T> boolean test(T t) T boolean Accepts 1 parameter, returns 1 boolean result
Supplier<T> T get() None T No parameter, return 1 result
UnaryOperator<T> extends Function<T, T> R apply(T t) T T Accept 1 parameter, return 1 result, parameters and result have the same type
BiFunction<T, U, R> R apply(T t, U u) T, U R Accepts 2 parameters, returns 1 result
BinaryOperator<T> extends BiFunction<T ,T, T> R apply(T t1, T t2) T, T T Accepts 2 parameters, returns 1 result, parameters and result have the same type

Consumer

/* Consumer source code */

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { 
            // Call the accept method of current Consumer object
            accept(t);
            // Call the accept method of next Consumer object
            after.accept(t); 
        };
    }
}

accept

To define a Consumer object, the traditional way is as follows:

Consumer c = new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o);
    }
};

Consumer<Integer> c2 = new Consumer<Integer>() {
    @Override
    public void accept(Integer x) {
        x = x + 1;
        System.out.println(x);
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

Consumer c = (Object o) -> {
    System.out.println(o);
};

Consumer<Integer> c2 = (Integer x) -> {
    x = x + 1;
    System.out.println(x);
};

Further simplify:

Consumer c = (o) -> {
    System.out.println(o);
};

Consumer<Integer> c2 = (x) -> {
    x = x + 1;
    System.out.println(x);
};

Further simplify:

Consumer c = o -> {
    System.out.println(o);
};

Consumer<Integer> c2 = x -> {
    x = x + 1;
    System.out.println(x);
};

As explained above, functional interface has single (one and only) abstract method. So the compiler will automatically implement this abstract method. If the interface has multiple abstract methods, the compiler does not know which method should be implemented. Therefore, the body of the function after = can be regarded as the implementation of the accept method.

  • Input: The part before -> surrounded by (). Consumer has only one input parameter, and the number of input parameters is determined by different functional interfaces. For example, functional interface with two parameters are written as: (a, b), no parameter is written as: ().
  • Function body: The part after -> is surrounded by {}. It can be a piece of code.
  • Output: Functional programming can have return value or no return value.

When there is only one statement in the function body, {} can be removed for further simplification:

Consumer c = o -> System.out.println(o);

Consumer<Integer> c2 = x -> System.out.println(x + 1);

However, this is not the simplest, here is just calling the println static method in System.out class to directly print the parameter, so it can be further simplified as follow:

/** 
 * Call the static method println in System.out to print the parameter
 * 1. System.out is class
 * 2. println is a method in System.out class
 * The type of parameter and return matches, which can be simplified:
 */
Consumer c = System.out::println;

// The type of parameter and return matches, but the return is x + 1,
// so the simplification cannot be continued

Through the last piece of code, we can simply understand functional programming. The Consumer interface can be directly regarded as a function. This function receives an parameter and processes it. In essence, it is still an object, but we have omitted the object definition process in the old way, and directly use a piece of code to assign values to the functional interface object. And most importantly, because this functional object is still an object, it can be used as a parameter or return value of other methods.

andThen

Definition: Whether to call another Consumer after calling the current Consumer.

private static void consumerAndThen() {
    Consumer f1 = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-f2");
    Consumer f3 = n -> System.out.println(n + "-f3");

    // Execute: f1.accept("test1"); -> f2.accept("test1" + "-f2"); -> f3.accept("test1" + "-f3");
    f1.andThen(f2).andThen(f3).accept("test1");

    // Execute: f1.accept("test2"); -> f1.accept("test2"); -> f1.accept("test2"); -> f1.accept("test2");
    f1.andThen(f1).andThen(f1).andThen(f1).accept("test2");
}

/* Output */
test1
test1-f2
test1-f3

test2
test2
test2
test2

Example 1

private static void consumerExample1(Consumer<String> one, Consumer<String> two) {
    one.andThen(two).accept("Hello");
}

public static void main(String[] args) {
    consumerExample1(
        s -> System.out.println(s.toUpperCase()),
        s -> System.out.println(s.toLowerCase()));
}

/* Output */
HELLO
hello

Example 2: Formatted print information

private static void formatPrint(Consumer<String> one, Consumer<String> two, String[] array) {
    for (String info : array) {
        one.andThen(two).accept(info);
    }
}

public static void main(String[] args) {
    String[] array = {"Liam,Male", "Emma,Female", "Noah,Male"};
    formatPrint(
        s -> System.out.print("Name: " + s.split(",")[0]),
        s -> System.out.println(", Gender: " + s.split(",")[1]),
        array);
}

/* Output */
Name: Liam, Gender: Male
Name: Emma, Gender: Female
Name: Noah, Gender: Male

Function

/* Function source code */
@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

To define a Function object, the traditional way is as follows:

Function f = new Function() {
    @Override
    public Object apply(Object o) {
        return o;
    }
};

// The first is parameter type, the second is return type
Function<Integer, Boolean> f2 = new Function<Integer, Boolean>() {
    @Override
    public Boolean apply(Integer i) {
        return i >= 0;
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

Function f = (Object o) -> {
    return o;
};

Function<Integer, Boolean> f2 = (Integer x) -> {
    return x >= 0;
};

Further simplify:

Function f = (o) -> {
    return o;
};

Function<Integer, Boolean> f2 = (x) -> {
    return x >= 0;
};

Further simplify:

Function f = o -> {
    return o;
};

Function<Integer, Boolean> f2 = x -> {
    return x >= 0;
};

When there is only one statement in the function body, {} can be removed for further simplification:

Function f = o -> o;

Function<Integer, Boolean> f2 = x -> x >= 0;

Example 1

public static void functionExample1() {
     Function<Integer, Integer> f = s -> s + 1;
     Function<Integer, Integer> g = s -> s * 2;

    /**
     * Indicates that execute g before f, and use the return of g as the parameter of f
     * fog(x) = f(g(x))
     * fog(1) = f(g(1)) = f(2) = 3
     * It is equivalent to the following code:
     * Integer a = g.apply(1);
     * System.out.println(f.apply(a));
     */
    System.out.println(f.compose(g).apply(1));

    /**
     * Indicates that after executing f, the return value is used as the parameter to execute g
     * gof(x) = g(f(x))
     * gof(1) = g(f(1)) = g(2) = 4
     * It is equivalent to the following code:
     * Integer a = f.apply(1);
     * System.out.println(g.apply(a));
     */
    System.out.println(f.andThen(g).apply(1));

    /**
     * The identity method returns a Function without any processing, that is, the parameter is equal to the return
     */
    System.out.println(Function.identity().apply("a"));
}

/* Output */
3
4
a

Predicate

/* Predicate source code */
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

To define a Predicate object, the traditional way is as follows:

Predicate p = new Predicate() {
    @Override
    public boolean test(Object o) {
        return o.equals("abc");
    }
};

Predicate<Integer> p2 = new Predicate<Integer>() {
    @Override
    public boolean test(Integer i) {
        return i >= 0;
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

Predicate p = (Object o) -> {
    return o.equals("abc");
};

Predicate<Integer> p2 = (Integer x) -> {
    return x >= 0;
};

Further simplify:

Predicate p = (o) -> {
    return o.equals("abc");
};

Predicate<Integer> p2 = (x) -> {
    return x >= 0;
};

Further simplify:

Predicate p = o -> {
    return o.equals("abc");
};

Predicate<Integer> p2 = x -> {
    return x >= 0;
};

When there is only one statement in the function body, {} can be removed for further simplification:

Predicate f = o -> o.equals("abc");
// equals to
Predicate f = o -> "abc".equals(o);

Predicate<Integer> p2 = x -> x >= 0;

However, this is not the simplest, here is just calling the equals static method in String class, so it can be further simplified as follow:

/* Call the static method equals in String */
 * 1. String is class
 * 2. equals is a method in String class
 * The type of parameter and return matches, which can be simplified:
 */
Predicate f = "abc"::equals;

// The type of parameter and return matches, but the return is x >= 0,
// so the simplification cannot be continued

test

/**
 * Find all eligible elements
 */
public static void predicateTest(List<Integer> list, Predicate<Integer> predicate) {
    for (Integer n : list) {
        if (predicate.test(n)) {
            System.out.print(n + " ");
        }
    }
}

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    System.out.println("\nAll even numbers: ");
    predicateTest(list, n -> n % 2 == 0);
    System.out.println("\nAll numbers greater than 3: ");
    predicateTest(list, n -> n > 3);
}

/* Output */
All even numbers: 
2 4 6 8 
All numbers greater than 3: 
4 5 6 7 8 9 

and

/**
 * A string must contain both uppercase "H" and uppercase "W"
 */
private static void predicateAnd(Predicate<String> one, Predicate<String> two) {
    boolean isValid = one.and(two).test("Helloworld");
    System.out.println("Does the string meet the requirements: " + isValid);
}

public static void main(String[] args) {
    predicateAnd(s -> s.contains("H"), s -> s.contains("W"));
}

/* Output */
Does the string meet the requirements: false

or

/**
 * A string must contain uppercase "H" or uppercase "W"
 */
private static void predicateOr(Predicate<String> one, Predicate<String> two) {
    boolean isValid = one.or(two).test("Helloworld");
    System.out.println("Does the string meet the requirements: " + isValid);
}

public static void main(String[] args) {
    predicateOr(s -> s.contains("H"), s -> s.contains("W"));
}

/* Output */
Does the string meet the requirements: true

negate

/**
 * Invert the original Predicate
 */
public static void predicateNegate(List<Integer> list, Predicate<Integer> predicate) {
    for (Integer n : list) {
        if (predicate.negate().test(n)) {
            System.out.print(n + " ");
        }
}

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    System.out.println("\nAll odd numbers: ");
    predicateTest2(list, n -> n % 2 == 0);
    System.out.println("\nAll numbers less than or equal to 3: ");
    predicateTest2(list, n -> n > 3);
}

/* Output */
All odd numbers: 
1 3 5 7 9
All numbers less than or equal to 3: 
1 2 3

Example 1

private static void predicateExample1() {
    Predicate<String> p = o -> o.equals("test");
    Predicate<String> g = o -> o.startsWith("t");

    /**
     * negate: Invert the original Predicate
     * When calling p.test("test") is True, then calling p.negate().test("test") will be False
     */
    System.out.println(p.negate().test("test"));

    /**
     * and: For the same input value, if multiple Predicates all return True, 
     * then the result returns True, otherwise returns False
     */
    System.out.println(p.and(g).test("test"));

    /**
     * or: For the same input value, as long as one of multiple Predicates returns True, 
     * then the result returns True, otherwise it returns False
     */
    System.out.println(p.or(g).test("ta"));
}

/* Output */
false
true
true

Example 2 Find all the elements whose gender is female and whose name is 5 letters

private static List<String> filter(String[] array, Predicate<String> one,
                                       Predicate<String> two) {
        List<String> list = new ArrayList<>();
        for (String info : array) {
            if (one.and(two).test(info)) {
                list.add(info);
            }
        }
        return list;
    }

public static void main(String[] args) {
    String[] array = {"James,female", "Amelia,female", "Henry,male", "Logan,female"};
    List<String> list = filter(array,
        s -> "female".equals(s.split(",")[1]),
        s -> s.split(",")[0].length() == 5);
    System.out.println(list);
}

/* Output */
[James,female, Logan,female]

Supplier

/* Supplier source code */
@FunctionalInterface
public interface Supplier<T> {

    T get();
}

To define a Supplier object, the traditional way is as follows:

Supplier s = new Supplier() {
    @Override
    public Object get() {
        return "abc".toLowerCase();
    }
};

Supplier<String> s2 = new Supplier<String>() {
    @Override
    public String get() {
        return "abc".toString();
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

Supplier s = () -> {
    return "abc".toLowerCase();
};

Supplier<String> s2 = () -> {
    return "abc".toString();
};

When there is only one statement in the function body, {} can be removed for further simplification:

Supplier s = () -> "abc".toLowerCase();

Supplier<String> s2 = () -> "abc"::toString;

However, this is not the simplest, here is just calling the toLowerCase static method in String class, so it can be further simplified as follow:

/* Call the static method toLowerCase in String */
 * 1. String is class
 * 2. toLowerCase is a method in String class
 * String.toLowerCase and String.toString don't have parameters, which can be simplified:
 */
Supplier s = "abc"::toLowerCase;

Supplier<String> s2 = "abc"::toString;

Example 1

private static String supplierExample1(Supplier<String> supplier) {
    return function.get(); 
}

public static void main(String[] args) {
    String msgA = "Hello ";
    String msgB = "World"; 
    System.out.println(getString(() -> msgA + msgB));
}

/* Output */
Hello World

Example 2 Find the maximum value of an array element

public static int getMax(Supplier<Integer> supplier) {
    return supplier.get();
}

public static void main(String[] args) {
    int arr[] = {21, 23, 24, 52, 34, 213};
    int maxNum = getMax(() -> {
        // Find the maximum value
        int max = arr[0];
        for (int i : arr) {
            if (i > max) {
                max = i;
            }
        }
        return max;
    });
    System.out.println(maxNum);
}

/* Output */
213

UnaryOperator

/* UnaryOperator source code */
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

To define a UnaryOperator object, the traditional way is as follows:

UnaryOperator u = new UnaryOperator() {
    @Override
    public Object apply(Object o) {
        return o;
    }
};

UnaryOperator<Integer> u2 = new UnaryOperator<Integer>() {
    @Override
    public Integer apply(Integer i) {
        return i * i;
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

UnaryOperator u = (Object o) -> { 
    return o;
};

UnaryOperator<Integer> u2 = (Integer i) -> {
    return i * i;
};

When there is only one statement in the function body, {} can be removed for further simplification:

UnaryOperator u = (Object o) -> o;

UnaryOperator<Integer> u2 = (Integer i) -> i * i;

Example 1 Find Square

public static int unaryOperatorExample1(UnaryOperator<Integer> unaryOperator, int i) {
    return unaryOperator.apply(i);
}

public static void main(String[] args) {
    System.out.println(unaryOperatorExample1(x -> x * x, 10));
}

/* Output */
100

BiFunction

/* BiFunction source code */
@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

To define a BiFunction object, the traditional way is as follows:

BiFunction b = new BiFunction() {
    @Override
    public Object apply(Object o1, Object o2) {
        return o1.equals(o2) ? o1 : o2;
    }
};

// The first two are parameter type, the third is return type
BiFunction<Integer, Integer, Integer> b2 = new BiFunction<Integer, Integer, Integer>() {
    @Override
    public Integer apply(Integer i1, Integer i2) {
        return i1 + i2;
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

BiFunction b = (Object o1, Object o2) -> {
    return o1.equals(o2) ? o1 : o2;
};

BiFunction<Integer, Integer, Integer> b2 = (Integer i1, Integer i2) -> { 
    return i1 + i2;
};

Further simplify:

BiFunction b = (o1, o2) -> {
    return o1.equals(o2) ? o1 : o2;
};

BiFunction<Integer, Integer, Integer> b2 = (i1, i2) -> {
    return i1 + i2;
};

When there is only one statement in the function body, {} can be removed for further simplification:

BiFunction b = (o1, o2) -> o1.equals(o2) ? o1 : o2;

BiFunction<Integer, Integer, Integer> b2 = (i1, i2) -> i1 + i2;

Further simplify:

// The type of parameter and return mismatches,
// so the simplification cannot be continued

/** 
 * Call the static method sum in Integer to calculate the sum
 * 1. Integer is class
 * 2. sum is a method in Integer class
 * The type of parameter and return matches, which can be simplified:
 */
BiFunction<Integer, Integer, Integer> b2 = Integer::sum;

andThen

Definition: Take the result returned by BiFunction as the parameter of Function and get a new result.

// (1 + 2) * 10 = 30
BiFunction<Integer, Integer, Integer> b = Integer::sum;
int result = b.andThen(i -> i * 10).apply(1, 2);
System.out.println(result);

/* Output */
30

Example 1

public static void biFunctionExample(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
    Consumer<Integer> c = System.out::println;
    c.accept(biFunction.apply(a, b));
}

public static void main(String[] args) {
    biFunctionExample(1, 2, Integer::sum);
    biFunctionExample(1, 2, (i1, i2) -> i1 - i2);
    biFunctionExample(1, 2, (i1, i2) -> i1 * i2);
    biFunctionExample(1, 2, (i1, i2) -> i1 / i2);
}

/* Output */
3
-1
2
0

Example 2

public static void biFunctionExample2(int a, int b, 
                                      BiFunction<Integer, Integer, Integer> biFunction, 
                                      Function<Integer, Integer> function) {
    Consumer<Integer> c = System.out::println;
    c.accept(biFunction.andThen(function).apply(a, b));
}

public static void main(String[] args) {
    biFunctionExample2(1, 2, Integer::sum, i -> i * 10);
    biFunctionExample2(1, 2, (i1, i2) -> i1 - i2, i -> i * 10);
    biFunctionExample2(1, 2, (i1, i2) -> i1 * i2, i -> i * 10);
    biFunctionExample2(1, 2, (i1, i2) -> i1 / i2, i -> i * 10);
}

/* Output */
30
-10
20
0

BinaryOperator

/* BinaryOperator source code */
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {

    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

To define a BinaryOperator object, the traditional way is as follows:

BinaryOperator b = new BinaryOperator() {
    @Override
    public Object apply(Object o1, Object o2) {
        return o1.equals(o2) ? o1 : o2;
    }
};
        
BinaryOperator<Integer> b2 = new BinaryOperator<Integer>() {
    @Override
    public Integer apply(Integer i1, Integer i2) {
        return i1 + i2;
    }
};

In Java 8, for functional programming interface, it can be defined as follows:

BinaryOperator b = (Object o1, Object o2) -> {
    return o1.equals(o2) ? o1 : o2;
};

BinaryOperator<Integer> b2 = (Integer i1, Integer i2) -> {
    return i1 + i2;
};

Further simplify:

BinaryOperator b = (o1, o2) -> {
    return o1.equals(o2) ? o1 : o2;
};

BinaryOperator<Integer> b2 = (i1, i2) -> {
    return i1 + i2;
};

When there is only one statement in the function body, {} can be removed for further simplification:

BinaryOperator b = (o1, o2) -> o1.equals(o2) ? o1 : o2;

BinaryOperator<Integer> b2 = (i1, i2) -> i1 + i2;

Further simplify:

// The type of parameter and return mismatches,
// so the simplification cannot be continued

/** 
 * Call the static method sum in Integer to calculate the sum
 * 1. Integer is class
 * 2. sum is a method in Integer class
 * The type of parameter and return matches, which can be simplified:
 */
BinaryOperator<Integer> b2 = Integer::sum;

minBy

Definition: Return the smaller of the two elements according to the specified Comparator.

BinaryOperator<Integer> b = BinaryOperator.minBy(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

public static void main(String[] args) {
    System.out.println(b.apply(2, 3));
}

/* Output */
2

maxBy

Definition: Return the larger of the two elements according to the specified Comparator.

BinaryOperator<Integer> b = BinaryOperator.minBy(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

public static void main(String[] args) {
    System.out.println(b.apply(2, 3));
}

/* Output */
3

Example 1

public static void binaryOperatorExample(int a, int b, BinaryOperator<Integer> binaryOperator) {
    Consumer<Integer> c = System.out::println;
    c.accept(binaryOperator.apply(a, b));
}

public static void main(String[] args) {
    binaryOperatorExample(1, 2, Integer::sum);
    binaryOperatorExample(1, 2, (i1, i2) -> i1 - i2);
    binaryOperatorExample(1, 2, (i1, i2) -> i1 * i2);
    binaryOperatorExample(1, 2, (i1, i2) -> i1 / i2);
}

/* Output */
3
-1
2
0
Clone this wiki locally