[Sleeping JDK] Detailed explanation of Java functional programming interface Consumer, Function

Today we will introduce Java’s other two function programming interfaces, Consumer and Function. What do these two functions do? Let’s take a look at the official definition first:

  • Consumer: Represents an operation that accepts a single input parameter but does not return a result.
  • Function: Represents a function that accepts one parameter and generates a result.

一、Consumer

1.1 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) -> {
    
     accept(t); after.accept(t); };
    }
}

1.2 Look familiar

In fact, we often use Consumer. Look at the following example:

List<String> list = Arrays.asList("1", "2", "3");
list.forEach(System.out::println);

The forEach function we often use is actually implemented through Consumer, so it is necessary to master Consumer. Let’s take a look at the implementation of forEach in ArrayList:

public void forEach(Consumer<? super E> action) {
    
    
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
    
    
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
    
    
        throw new ConcurrentModificationException();
    }
}

No need to introduce it, I think everyone can understand it. Consumer represents a custom operation. If the operation is passed as a parameter to another function, the custom operation can be performed in the function. The above for loop code is equivalent to:

for (int i=0; modCount == expectedModCount && i < size; i++) {
    
    
    System.out.println(elementData[i]);
}

1.3 Implementation

If the operation is common or general, you can use a class to implement Consumer, save the operation, and use it quickly when necessary.

// 实体类
public class Person {
    
    
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
    
    
        this.name = name;
        this.age = age;
    }
    //...
}
// 打印小孩
public class PrintChild implements Consumer<Person> {
    
    
    @Override
    public void accept(Person person) {
    
    
        if (person.getAge() < 18) {
    
    
            System.out.println(person.getName() + "还是个孩子啊");
        }
    }
}

// 测试代码
List<Person> list1 = Arrays.asList(
                new Person("大壮", 19),
                new Person("小光", 16),
                new Person("小小", 15));
list1.forEach(new PrintChild());

1.4 Introduction to andThen function

Function source code:

default Consumer<T> andThen(Consumer<? super T> after) {
    
    
    Objects.requireNonNull(after);
    return (T t) -> {
    
     accept(t); after.accept(t); };
}

The function of the andThen function is to merge two Consumer operations and return a new Consumer. It is used as follows:

// 打印年龄
public class PrintAge implements Consumer<Person> {
    
    
    @Override
    public void accept(Person person) {
    
    
        System.out.println(person.getName() + "的年龄是:" + person.getAge() + "岁");
    }
}

// 测试代码
List<Person> list1 = Arrays.asList(
                new Person("小雨", 19),
                new Person("小光", 16),
                new Person("小小", 15));
list1.forEach(new PrintChild().andThen(new PrintAge()));

// 结果
小雨的年龄是:19岁
小光是个孩子
小光的年龄是:16岁
小小是个孩子
小小的年龄是:15

1.5 Other variations of Consumer

Interface name parameter Return type describe
BiConsumer (T, U) void BiConsumer accepts two parameters
DoubleConsumer double void Accepts a parameter of type double
IntConsumer int void Accepts a parameter of type int
LongConsumer long void Accepts a parameter of type long
ObjDoubleConsumer (T, double) void Accepts an Object type and a double type parameter
ObjIntConsumer (T, int) void Accepts an Object type and an int type parameter
ObjLongConsumer (T, long) void Accepts an Object type and a long type parameter

2.Function

Function and Consumer are functionally identical, but Function returns results.

2.1 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;
    }
}

2.2 Look familiar

We also use Function frequently, here:

List<Person> list1 = Arrays.asList(
                new Person("小雨", 19),
                new Person("小光", 16),
                new Person("小小", 15));
List<String> list = list1.stream()
    .map(Person::getName)
    .collect(Collectors.toList());

If you have read another article written by the author ( Revisiting Java 8 Functional Programming ), then you should be able to understand the following code. In fact, the above map operation can be restored to:

List<String> list = list1.stream()
                .map(person -> {
    
    
                    return person.getName();
                })
                .collect(Collectors.toList());

The map encapsulates a function with a return value, and the function is passed into the map as a parameter.

2.3 Introduction to compose function

Function source code:

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

The function of this function is to combine two Functions. The parameter Function before is executed first, and the result after execution is handed over to the calling Function for execution.

// 加法
public class AddFunc implements Function<Integer, Integer> {
    
    
    private int origin;

    public AddFunc(int origin) {
    
    
        this.origin = origin;
    }

    @Override
    public Integer apply(Integer integer) {
    
    
        return this.origin + integer;
    }
}

// 减法
public class ReduceFunc implements Function<Integer, Integer> {
    
    
    private int origin;
    private boolean isMinuend;// origin被减数与否

    public ReduceFunc(int origin, boolean isMinuend) {
    
    
        this.origin = origin;
        this.isMinuend = isMinuend;
    }

    @Override
    public Integer apply(Integer integer) {
    
    
        return isMinuend ? this.origin - integer : integer - this.origin;
    }
}

// 测试代码
public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 计算 1 + (2 - 3)
        System.out.println(handle(new AddFunc(1).compose(new ReduceFunc(2, true)), 3));
    }

    public static int handle(Function<Integer, Integer> function, Integer integer) {
    
    
        return function.apply(integer);
    }
}

2.4 Introduction to andThen function

Function source code:

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

The andThen function also combines two Functions, but is executed after the parameter Function.

// 测试代码
public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 计算 1 + 2 - 4
        System.out.println(handle(new AddFunc(1).andThen(new ReduceFunc(4, false)), 2));
    }

    public static int handle(Function<Integer, Integer> function, Integer integer) {
    
    
        return function.apply(integer);
    }
}

2.5 identity function

Source code:

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

Well, the function of this function is to return a Function with the same input and output, so what is the use of this function? See example:

// 示例:将list转变为map
List<Person> list = Arrays.asList(
                new Person("xiaoxiao", 11),
                new Person("dazhuang", 15));
Map<String, Person> map = list.stream()
    .collect(Collectors.toMap(Person::getName, person -> person));

// 使用identity函数
List<Person> list = Arrays.asList(
                new Person("xiaoxiao", 11),
                new Person("dazhuang", 15));
Map<String, Person> map = list.stream()
    .collect(Collectors.toMap(Person::getName, Function.identity()));

Both methods of use are possible, but which method do you think is more charming (bi) and ge (ge)?

2.6 What can we do

  • After mastering Function, when writing public component logic, you can throw part of the logic up and let the caller implement its own features, which can enhance the flexibility of the component.
  • Implement the Function interface and define some common operations to reduce code redundancy.

The basis of application is to master it first. Only by mastering it can you use it skillfully. This is a cup of poisonous chicken soup. (Quietly BB: The last day of the Dragon Boat Festival is off, T . T)

2.7 Other variants of Function

Interface name parameter Return type describe
BiFunction (T, U) R BiFunction accepts two parameters
DoubleFunction double R Accepts a parameter of type double
DoubleToIntFunction double int Accepts double type parameters and returns int type results
DoubleToLongFunction double long Accepts double type parameters and returns long type results
IntFunction int R Accepts a parameter of type int
IntToDoubleFunction int double Accepts int type parameters and returns double type results
IntToLongFunction int long Accepts int type parameters and returns long type results
LongFunction long R Accepts a parameter of type long
LongToDoubleFunction long double Accepts long type parameters and returns double type results
LongToIntFunction long int Accepts long type parameters and returns int type results
ToDoubleBiFunction (T, U) double Accepts two parameters and returns a double type result
ToDoubleFunction T double Accepts an Object type and returns a double type parameter
ToIntBiFunction (T, U) int Accepts two parameters and returns an int type result
ToIntFunction T int Accepts an Object type and returns an int type parameter
ToLongBiFunction (T, U) long Accepts two parameters and returns long type result
ToLongFunction T long Accepts an Object type and returns a long type parameter

Article recommendation:

[Sleeping JDK] Detailed explanation of Java functional programming interface Predicate

[Sleeping JDK] Detailed explanation of Java functional programming interface Supplier

[Sleeping JDK] Detailed explanation of Java functional programming interface UnaryOperator, BinaryOperator

end
Java Development Paradise

Guess you like

Origin blog.csdn.net/u012534326/article/details/107031007