函数式接口

什么是函数式接口呢?函数式接口是只有一个抽象方法(Object的方法除外),但是可以有多个非抽象方法的接口,它表达的是一种逻辑上的单一功能。

4eaa3f39e182273133474463dd9795da.png

@FunctionalInterface

@FunctionalInterface 注解用来表示该接口是函数式接口。它有助于及早发现函数式接口中出现的或接口继承的不适当的方法声明。

如果接口用该注解来注释,但实际上不是函数式接口,则会在编译时报错。

Consumer

我们一般称之为“消费者”,它表示接受单个输入参数但不返回结果的操作。不同于其它函数式接口,Consumer 预期通过副作用进行操作。

那什么又是副作用呢?说一下我所理解的副作用,副作用其实就是一个函数是否会修改它范围之外的资源,如果有就叫有副作用,反之为没有副作用。比如修改全局变量,修改输入参数所引用的对象等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface
public interface Consumer<T> {

/**
* 对给定的参数执行此操作。
*/
void accept(T t);

/**
*
* 返回一个组合的 Consumer ,依次执行此操作,然后执行after操作。
* 如果执行任一操作会抛出异常,它将被转发到组合操作的调用者。
* 如果执行此操作会引发异常,则不会执行after操作。
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

正如我们案例中遇到的场景,我们只需要将要执行的逻辑方法当作参数传入 getResponse() 中,然后在该方法中执行 accept() 方法进行消费即可。如果还不理解,我们可以把它转换为匿名内部类的调用方式。

1
2
3
4
5
6
getResponse(dto, response, new Consumer<B>() {
@Override
public void accept(B bb) {
mapper.insert(bb);
}
});

当调用accept() 方法的时候就会去调用匿名内部类的方法了,也就是我们传入 getResponse() 的逻辑方法。

例子:

1
2
3
4
5
6
7
8
9
10
11
public static String get(Pattern pattern, CharSequence content, int groupIndex) {
if (null != content && null != pattern) {
MutableObj<String> result = new MutableObj();
get(pattern, content, (matcher) -> {
result.set(matcher.group(groupIndex));
});
return (String)result.get();
} else {
return null;
}
}
1
2
3
4
5
6
7
8
9
public static void get(Pattern pattern, CharSequence content, Consumer<Matcher> consumer) {
if (null != content && null != pattern && null != consumer) {
Matcher m = pattern.matcher(content);
if (m.find()) {
consumer.accept(m);
}

}
}

上面get方法使用Consumer函数式接口,不过传入参数不是消费pattern或者content类型,matcher在下面中给出了,从content转化而来,当条件满足时执行result.set(matcher.group(groupIndex));matcher是Matcher m = pattern.matcher(content);产生,这里当consumer.accept(m)满足条件时执行result.set(matcher.group(groupIndex))

Supplier

我们一般称之为“生产者”,没有参数输入,但是能返回结果,为结果的提供者。

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface Supplier<T> {

/**
* 获取一个结果
*/
T get();
}

可以举个简单的例子感受下:

1
2
3
4
5
6
7
Optional<Double> optional = Optional.empty();
optional.orElseGet(()->Math.random() );

//orElseGet 方法的源码,里边用到了 get 方法
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

Function

我把它称为“转换者”,表示接收一个参数通过处理之后返回一个结果的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@FunctionalInterface
public interface Function<T, R> {

/**
* 将 T 类型的参数传入,经过函数表达式的计算,返回 R 类型的结果
*/
R apply(T t);

/**
* 返回一个组合函数,先将参数应用于 before 函数,然后将结果应用于当前函数,返回最终结果。
* 如果对任一函数的求值引发异常,则会将其转发给组合函数的调用方。
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}

/**
* 返回一个组合函数,先将参数应用与当前函数,然后将结果应用于 after 函数,返回最终的结果。
* 如果对任一函数的求值引发异常,则会将其转发给组合函数的调用方。
*/
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;
}
}

我们在 lambda 表达式中应用比较多,所以我们来简单演示下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@AllArgsConstructor
public class Teacher {
private String name;
private int age;
}

public class TeacherTest {
public static void main(String[] args) {
List<Teacher> list = Arrays.asList(
new Teacher("张三",25),
new Teacher("李四",28),
new Teacher("王五",18));
List<String> collect = list.stream().map(item -> item.getName()).collect(Collectors.toList());
System.out.println(collect);
}
}

其中 map 接收的参数就是 Function 类型, item 为传入参数,item.getName() 为返回处理的结果,最后输出结果为

1
[张三, 李四, 王五]

Predicate

我们称之为“判断者”,通过接收参数 T 来返回 boolean 的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@FunctionalInterface
public interface Predicate<T> {

/**
* 接收一个参数, 判断这个参数是否匹配某种规则, 匹配成功返回true, 匹配失败则返回false
*/
boolean test(T t);

/**
* 接收一个 Predicate 类型的参数,用当前函数和 other 函数逻辑与判断参数 t 是否匹配规则,成功返回true,失败返回 false
* 如果当前函数返回 false,则 other 函数不进行计算
* 在评估 Predicate 期间引发的任何异常都会转发给调用方
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}

/**
* 返回当前Predicate取反操作之后的Predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}

/**
* 接收一个 Predicate 类型的参数,用当前函数和 other 函数 逻辑或 判断参数 t 是否匹配规则,成功返回true,失败返回 false
* 如果当前函数返回 true,则 other 函数不进行计算
* 在评估 Predicate 期间引发的任何异常都会转发给调用方
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

/**
* 静态方法:传入一个参数,用来生成一个 Predicate,调用test() 方法时调的 object -> targetRef.equals(object) 函数式
*
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

相信大家在编码过程中经常会遇到该函数式接口,我们举个例子来说一下:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
List<Teacher> list = Arrays.asList(
new Teacher("张三",25),
new Teacher("李四",28),
new Teacher("王五",18));

list = list.stream().filter(item -> item.getAge()>25).collect(Collectors.toList());
list.stream().forEach(item->System.out.println(item.getName()));
}

其中 filter() 的参数为 Predicate 类型的,返回结果为:李四

看到这儿,我们常见的四种函数式接口就已经介绍完了。说实话,函数式接口我已经看过好几遍了,尤其是 ConsumerSupplier。当时只是脑子里学会了,没有应用到具体的项目中,下次再遇到的时候还是一脸懵逼,不知道大家有没有这种感受。

所以我们需要总结经验教训,一定要将代码的原理搞懂,然后多敲几遍,争取应用到自己的项目中,提升自己的编码能力。

推荐阅读:

《吃透 MQ 系列》之核心基础篇

Redis 宕机,数据丢了,老板要辞退我

程序员必备基础:10种常见安全漏洞浅析

网关技术选型,为什么选择 Openresty ?

Redis主节点的Key已过期的处理