Java基础:JDK8 四大核心函数式接口详解与应用
函数式接口 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口(default 修饰),这又关联了 Java 8 的另一个新特性,允许在接口中定义默认方法(default 修饰)。
函数式接口是行为的抽象,是数据转换。其最直接表现是将函数(行为)作为数据传递进方式中。
函数式编程的一大好处,是可以用更精练的代码来表达常用数据处理。函数接口能够轻易地实现模板方法模式,只要将不确定的业务逻辑抽象成函数接口,然后传入不同的 Lambda 表达式即可。
JDK 8 新特性中的 Lambda 表达式是 Java 8 发布的最重要的特性,Lambda 表达式允许把函数作为一个方法的参数传递 (函数作为参数传递进方法中),但是只能用在重写函数式接口的抽象方法上。
与 Lambda 表达式功能一起发布的 函数式接口 是其重要组成部分。Lambda 表达式和方法引用就是针对 函数式接口的使用(即只能用在函数式接口上),因为 Lambda 表达式没有方法名,所以针对的就是接口中的抽象方法,则要求函数式接口中有且只有一个抽象方法,否则就不知道 Lambda 重写的是哪个抽象方法。
Lambda 表达式只能用在重写函数式接口的抽象方法上,因为函数式接口只有一个抽象方法,编译器会认为 Lambda 表达式写的方法就是接口的唯一的抽象方法。
JDK 8 以前的接口中的所有方法必须都是抽象的,不能有非抽象方法,其实现类必须实现其所有方法,而 JDK 在更新过程中可能对原有的接口进行扩展,若在原有接口中加入抽象方法,那实现了该接口的类要全部重写新加入的方法,为了解决更新接口同时又兼容以前版本的程序,就支持在接口中增加 default 方法,变相的让 Java 支持多继承,因为可以实现多个接口。
Java 中的 Lambda 表达式的功能更多应用在流数据处理中(负责迭代数据的集合)。
FunctionalInterface
JDK 8 新增了 @FunctionalInterface
注解,用于标识接口类型声明为函数式接口,从概念上讲,函数式接口只有一个抽象方法。可以通过 java.lang.reflect.Method#isDefault() 反射操作来判断是否为默认方法。
注意:函数式接口的实现可以使用 Lambda 表达式,方法引用或函数引用创建。
@FunctionalInterface 注解只能作用在接口类型上,不能在注解类型,枚举类型 或 类上使用且。作用的目标接口必须满足函数式接口的要求。
@FunctionalInterface 注解用于显式标注一个函数式接口,该注解主要用于编译级错误检查,使用了该注解后,如果自定义接口不满足函数式接口的要求,会报错。并不是所有函数式接口都必须使用该注解进行标识,编译器会把满足函数接口定义的任何接口视为函数接口,而不管接口声明中是否存在 @FunctionalInterface 注解。
4个核心函数式接口
JDK 8 在 java.util.function 包中定义了一些基础的函数式接口,其中有四个可认为是核心的函数式接口:
函数式接口 | 类型别名 | 描述 | 备注 |
---|---|---|---|
Function<T,R> |
函数型 | 数据转换器,接收一个 T 类型的对象, 返回一个 R 类型的对象,单参数单返回值的行为接口; 提供了 apply, compose, andThen, identity 方法 |
|
Supplier<T> |
供给型 | 数据提供器,可以提供 T 类型对象; 无参的构造器,提供了 get 方法 |
|
Consumer<T> |
消费型 | 数据消费器, 接收一个 T 类型的对象,无返回值; 通常用于设置 T 对象的值; 单参数无返回值的行为接口;提供了 accept, andThen 方法 |
|
Predicate<T> |
断定型 | 条件测试器,接收一个 T 类型的对象,返回布尔值; 通常用于传递条件函数; 单参数布尔值的条件性接口; 提供了 test (条件测试) , and-or- negate(与或非) 方法; 其中, compose, andThen, and, or, negate 用来组合函数接口 而得到更强大的函数接口。 |
其它的函数接口都是通过这四个扩展而来:
- 在参数个数上扩展: 比如接收双参数的,有 Bi 前缀, 比如
BiConsumer<T, U>
,BiFunction<T, U, R>
。 - 在类型上扩展: 比如接收原子类型参数的,有 [
Int|Double|Long|Function|Consumer|Supplier|Predicate
]。 - 特殊常用的变形: 比如 BinaryOperator , 是同类型的双参数
BiFunction<T, T, T>
,二元操作符 。UnaryOperator 是Function<T, T>
一元操作符。
函数式接口可以接收的值类型有:
- 类/对象的静态方法引用、实例方法引用。引用符号为双冒号
::
。 - 类的构造器引用,比如
Class::new
。 - Lambda 表达式。
Consumer接口
接口定义
对入参做一些操作,没有返回值(相当于内部消费掉传入的参数)。
在 Stream 里,主要用于内部 ForEach,内部迭代,对传入的参数做一系列的业务操作。
1 | /** |
使用示例
void accept(T t):
Lambda 表达式或方法引用相当于重写内部 accept(T t)
方法。
示例一:consumerTest1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void consumerTest1() {
List<String> list = Arrays.asList("Tom", "Kitty", "Rose", "Alex", "Andy");
// 匿名内部类(重写内部 accept 方法, 默认调用单一的抽象方法)
list.forEach(new Consumer<String>() {
public void accept(String s) {
System.out.println(s);
}
});
System.out.println("----------------------");
// Lambda 表达式(重写内部 accept 方法, 默认调用单一的抽象方法)
list.forEach(s -> System.out.println(s));
System.out.println("----------------------");
// 方法引用(重写内部 accept 方法, 默认调用单一的抽象方法)
list.forEach(System.out::println);
}结果输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Tom
Kitty
Rose
Alex
Andy
----------------------
Tom
Kitty
Rose
Alex
Andy
----------------------
Tom
Kitty
Rose
Alex
Andy示例二:consumerTest2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* 1.消费型 Consumer<T> void accept(T t)
*/
public void consumerTest2() {
// 匿名方法
Consumer<String> c1 = new Consumer<String>() {
public void accept(String str) {
System.out.println(str);
}
};
// Lambda 表达式
Consumer<String> c2 = str -> System.out.println(str);
// 方法引用
Consumer<String> c3 = System.out::println;
c3.accept("Hello World");
}输出结果:
1
2
3
4Hello World 1
Hello World 2
Hello World 3示例三:consumerTest3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public void happy(double money, Consumer<Double> con) {
consumer.accept(money);
}
public void consumerTest3() {
// Lambda
this.happy(200D, s -> System.out.println(s));
// 方法引用
this.happy(200D, System.out::println);
// 匿名方法
this.happy(200D, new Consumer<Double>() {
public void accept(Double money) {
System.out.println(money);
}
});
}输出结果:
1
2
3200.0
200.0
200.0
andThen(Consumer<? super T> after)
示例一:consumerAndThen1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void consumerAndThen1() {
String[] strArr = {"Tom:boy", "Kitty:girl", "Rose:girl"};
// 标准Lambda可以使用 lambda表达式替换
printInfo(strArr, (message) -> {
System.out.print("Name:" + message.split(":")[0] + "");
}, (message) -> {
System.out.println("Gender:" + message.split(":")[1] + "");
});
// Lambda 表达式
printInfo(strArr, (message) -> System.out.print("Name:" + message.split(":")[0] + ""),
(message) -> System.out.println("Gender:" + message.split(":")[1] + ""));
}
// 传入多个 Consumer,指定执行顺序
public void printInfo(String[] strArr, Consumer<String> con1, Consumer<String> con2) {
for (int i = 0; i < strArr.length; i++) {
con1.andThen(con2).accept(strArr[i]);
}
}输出结果:
1
2
3
4
5
6Name:TomGender:boy
Name:KittyGender:girl
Name:RoseGender:girl
Name:TomGender:boy
Name:KittyGender:girl
Name:RoseGender:girl示例二:consumerAndThen2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void consumerAndThen2() {
List<Integer> list = Arrays.asList(1, 3, 2, 6, 9, 5);
Consumer<Integer> consumer = x -> System.out.print(x);
// 组合 Consumer
Consumer<Integer> consumerAndThen = consumer.andThen(i -> {
System.out.println(", print:" + i);
});
list.forEach(x -> consumerAndThen.accept(x));
System.out.println("----------------------");
list.forEach(consumerAndThen::accept);
System.out.println("----------------------");
// 函数调用,调用默认的抽象方法accept
list.forEach(consumerAndThen);
System.out.println("----------------------");
for (Integer integer : list) {
consumerAndThen.accept(integer);
}
}输出结果:
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
27print:1
print:3
print:2
print:6
print:9
print:5
----------------------
print:1
print:3
print:2
print:6
print:9
print:5
----------------------
print:1
print:3
print:2
print:6
print:9
print:5
----------------------
print:1
print:3
print:2
print:6
print:9
print:5
Supplier接口
Consumer 函数式接口相当于消费者,Supplier 接口就相当于生产者了,只有一个 get()
抽象方法返回结果。
接口定义
1 | /** |
使用示例
T get()
1 | public class SupplierTest { |
Function接口
Function 接收参数,然后运算转换为结果返回。
接口定义
1 | /** |
使用示例
R apply(T t)
示例一
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
46
47
48
49
50
51
52
53
54
public void functionTest1() {
// 字符串
Function<Integer, Integer> fun1 = x -> x * 2;
System.out.println(fun1.apply(4));// 8
Function<Integer, String> fun2 = x -> x * 2 + "aa";
System.out.println(fun2.apply(4));//8dd
// 字符串对象
Function<String, String> strFun3 = (str) -> new String(str);
System.out.println(strFun3.apply("bb"));//bb
Function<String, String> strFun4 = String::new;
System.out.println(strFun4.apply("cc"));//cc
// 对象
Function<String, User> objFun5 = (str) -> new User(str);
System.out.println(objFun5.apply("dd").getName());//dd
Function<String, User> objFun6 = User::new;
System.out.println(objFun6.apply("ee").getName());//ee
}
public void functionTest2() {
// 1.函数型 Function<T,R> R apply(T t)
Function<String, Integer> function2 = s -> s.length();
Function<String, Integer> function1 = String::length;
System.out.println(function1.apply("hello world"));
System.out.println(function2.apply("hello world"));
//结果:11
}
// Function<T, R> 函数型接口
// 需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun) {
return fun.apply(str);
}
public void functionTest3() {
String newStr2 = strHandler("\t\t\t Hello World", (str) -> str.trim());
String newStr1 = strHandler("\t\t\t Hello World", String::trim);
System.out.println(newStr1);
System.out.println(newStr2);
String subStr = strHandler("\t\t\t Hello World", (str) -> str.trim().substring(0, 4));
System.out.println(subStr);
//Hello World
//Hello World
//Hell
}示例二
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
34public class FunctionTest1 {
public static void main(String[] args) {
FunctionTest fun = new FunctionTest();
// 传递行为,而不是传递值
System.out.println(fun.comput(1, result -> 2 * num));
System.out.println(fun.comput(2, result -> 5 + num));
System.out.println(fun.comput(3, Integer::intValue));
System.out.println(fun.convert(4, result -> num + "helloworld"));
}
public int comput(int num, Function<Integer, Integer> function) {
//apply ,传递的是行为
int result = function.apply(num);
return result;
}
public String convert(int num, Function<Integer, String> function) {
return function.apply(num);
}
// 对于之前只传递值的写法,几种行为就要定义几种写法。 现在可以使用上面的方式去 传递行为
public int method1(int num) {
return num + 1;
}
public int method2(int num) {
return num * 5;
}
public int method3(int num) {
return num * num;
}
}
Predicate接口
接口定义
1 | /** |
使用示例
boolean test(T t)
1 | public class PredicatePreTest { |
相关参考
Java基础:JDK8 四大核心函数式接口详解与应用
http://blog.gxitsky.com/2020/06/18/Java-jdk-15-function-interface/