jdk8-2-流初步以及Function详解

一 流初步

java 8提供一种更加方便的操作集合方式,即流~ Stream
Stream的概念其实类似于 liunx 操作系统的中的 PipeLine 的概念,可以将数据传输;

流 分为两种:

  1. 中间流:数据通过后还返回一个流,不是数据的终点
  2. 节点流:数据流入后不再返回流,操作结束,没有返回

使用java8 中的 List 来写一个例子,这个例子是将集合中的所有元素,全部变为大写,而后将元素逐一输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test3 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("min.zhu", "yi.yu", "nai.nai");
// list1.stream().map(item -> item.toUpperCase()).forEach(item -> System.out.println(item));
List<String> list2 = new ArrayList<>();
// 流的操作方式 类似 linux 的管道功能
// 流 分为: 中间流 和 节点流
// map 称之为 映射 与 mapper-reducer 阶段的map 功能是一致的,给定一个值,返回会一个由于之映射的值
list1.stream().map(item -> item.toUpperCase()).forEach(item -> list2.add(item));
// 函数引用的做法
// String::toUpperCase 其实 和 item -> item.toUpperCase()
/**
* 一致,都是有输入有输出,而 toUpperCase 的输入,就是调用他的实例对象
* 因为一个类是无法调用他的实例方法的,一定是实例才可以调用实例方法
*/
list1.stream().map(String::toUpperCase).forEach(System.out::println);
list1.stream().map(String::toUpperCase).forEach(list2::add);
}
}

java8 的调用流的方式,是使用集合的 stream() 方法, 这个方法会返回一个流,之后就可以调用 map() 方法对 list 中的元素做一定的处理。 map() 函数,还是返回了一个流,这个流也有 forEach 方法,这个时候我们还可以调用 forEach 方法对流中的数据进行逐一的处理。

需要注意的是,forEach 函数中, 需要传递的函数的参数是一个 Consumer 的函数式接口,这个接口的抽象方法 accpt() 是没有返回值的,所以,他不在可以返回一个可以继续操作的流。

而我们在看看 map 方法中,需要传递的是一个我们没有见过的 Function 的函数式接口,这个函数式接口,有两个参数,一个是输入参数,一个是返回值。他的唯一的抽象方法中,是接受一个参数,返回一个值。具体见下图,唯一的方法是 apply 方法,它是有返回值的,这一点和现实中的函数很像

image_1b5ng26f4el33qb92s1itsgpl9.png-43.2kB

我们之前说过,java8 中,新增了很多个函数式接口,分别用于不同的场景
如之前说的 Consumer 接口,他是没有返回值的一个场景,专用于处理数据
以及今天说的有返回值的 Function 接口

那么这个 Function 的函数式接口,就是我们今天的重点。

二 Functon 接口 以及 BiFunction简介

我们可以用一个例子来完整的说明问题:

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
/**
* 函数式接口 Function 的经典案例
* jdk 8 新增很多 函数式接口
* 之前看的 forEach 方法中的 Consumer 接口 ,他的作用就是 对给定的参数进行一系列操作,不返回值
* 而现在的 以及 Test3 中流的 map 方法中 Function 接口 ,他唯一的操作 是apply , 他有返回值,一个value 一个 Return ,与现实当中数学的函数很类似
*
* jdk 8 中,接口中 除了可以有 default 方法以外, 还可以有一个 static 方法, static 方法可以有默认的实现
* 可以参考 Function 这个函数式接口
*/
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest = new FunctionTest();
// 函数式接口,以及 lambda 表达式,让我们可以在函数操作的时候,传递函数,即传递行为
// 而之前的编程方法,行为必须已经确定,而后进行调用
// 故而 之前的编程方法称之为命令式编程,而后面的方法称之为 函数式编程
// 而 可以传递,或者 返回函数的函数,称之为高阶函数( js 中随处可见高阶函数
System.out.println(functionTest.compute(1, item -> 2 * item));
// 这种返回方式是 statement 方法
// 上面那种方式是 expression 方式
// 一个是一个标准的语句,而一个是一个表达式
/*
* 表达式是不需要分好结尾的
* 而使用 标准语句,则必须要 ; 而且要 {}
*/
System.out.println(functionTest.compute(1, item -> {return 2 * item;}));
System.out.println(functionTest.compute(2, item -> item * item));
System.out.println(functionTest.convert("hello", item -> item + " world !!"));
}
public int compute(int value, Function<Integer, Integer> function) {
return function.apply(value);
}
public String convert(String value , Function<String, String> function) {
return function.apply(value);
}
}

主要去看代码的注释,上面写完了所有重点的内容,我们再去看看 Function 这个函数式接口的实现,我们知道,java8 允许接口有 default 方法,而 Function 接口中有两个默认方法:

image_1b5ngi5f21mha13p0uu4d06851m.png-66.8kB

利用这个两个方法,可以做函数的组合,用一个函数的输出,作为一个函数的输入。

  • compose 方法,是先执行 before 这个function, 在执行本身这个function;
  • andThen 方法,则是先执行 Function 本体的 apply 方法,在执行传入的 after 的方法

我们来写个例子:

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
public class FunctionTest2 {
public static void main(String[] args) {
FunctionTest2 functionTest2 = new FunctionTest2();
// 12
System.out.println(functionTest2.compute(2, item -> item * 3, item -> item * item));
// 36
System.out.println(functionTest2.compute2(2, item -> item * 3, item -> item * item));
// 25
System.out.println(functionTest2.compute3(2, 3, (a,b)-> a+b , result -> result * result));
}
public int compute (int a, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
/**
* compose 就是 先执行 function2.apply
* 再将 function2.apply 的返回值,作为 function1 的输入
* 此时再执行 function1 apply
*/
return function1.compose(function2).apply(a);
}
public int compute2 (int a, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
/**
* compose 就是 先执行 function1.apply
* 再将 function1.apply 的返回值,作为 function2 的输入
* 此时再执行 function2 apply
*/
return function1.andThen(function2).apply(a);
}
/**
* 如果想做有两个参数的Function 就需要使用BiFunction
* 而 BiFunction 只有一个 andThen , andThen 的输入 是 Function,
* 因为是将 Apply 的结果返回,结果只有一个,所以 参数只能是 Function , 而不是 BiFunction
* 所以 BiFunction 方法不可能有 compose 方法
*
* 对应的 consumer 也有着 BiConsumer 同时,二者因为没有返回值,所以也只可能有 andThen 方法
* @return
*/
public int compute3 (int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
return biFunction.andThen(function).apply(a,b);
}

composeandThen 的功能可以从例子中很好的看出来,例子中还有一个新的接口叫 BiFunction, 这个函数式接口是给定两个输入,返回一个输出,解决了有两个参数的函数的问题,对应的,其实 Comsumer 接口也还有一个对应的 BiConsumer 接口。

而当我们去观察这俩 Bi 的接口,都只有一个 andThen 的接口,为啥没有 compose 方法呢?
给读者自己思考吧~

朱老师&敏哥 wechat
有惊喜,朋友🙄
我要拿铁不加糖.