目录
Java 8中新增的特性旨在帮助程序员写出更好的代码,其中对核心类库的改进是很关键的一部分。对核心类库的改进主要包括集合类的API和新引入的流(Stream),流使程序员得以站在更高的抽象层次上对集合进行操作。
1.从外部迭代到内部迭代
Java程序员在使用集合类时,一个通用的模式是在集合上进行迭代,然后返回处理的每一个元素。例如,我们在统计姓名为张三的学生数时,一般会写如下的代码:
int count = 0;
for (StudentDTO studentDTO : studentDTOList) {
if (studentDTO.getName().equals("张三")) {
count++;
}
}
System.out.println(count);
这样的代码需要写很多的样板代码,而且将for循环改造成并行方式运行也很麻烦,需要修改每个for循环才能实现。 for循环和iterator循环本质上都是串行化的外部迭代。
与此相对应的是Stream实现的内部迭代:
int count2 = (int) studentDTOList.stream().filter(studentDTO -> studentDTO.getName().equals("张三")).count();
每一步的操作都对应于Stream接口的一个方法。为了找出姓名为张三的学生,首先需要对Stream对象进行过滤:filter。过滤在这里是指 “只保留通过某项检查的对象”。由于Stream API的函数式编程风格,我们并没有改变集合的内容,而是描述出Stream里的内容,count用于计算给定的Stream里包含多少个对象。
2.内部迭代的实现机制
像filter这样只描述Stream,最终不产生新集合的方法叫作惰性求值方法;而像count这样最终会从Stream产生值的方法叫作及早求值方法。 判断一个操作时惰性求值还是及早求值很简单:只需要看它的返回值。如果返回值是Stream,那么就是惰性求值;如果返回值是另一个值或者为 空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值链,最后用一个及早求值的操作返回想要的结果,这正是它的合理之处。惰性求值不会进行遍历操作,因此不会输出:
studentDTOList.stream()
.filter(studentDTO -> {
System.out.println(studentDTO.getName() + "1");
return studentDTO.getName().equals("张三");
});
上述代码不会打印任何的信息,如果将同样的输出语句加入一个及早求值操作,就会输出
studentDTOList.stream()
.filter(studentDTO -> {
System.out.println(studentDTO.getName() + "2");
return studentDTO.getName().equals("张三");
}).count();
3.常用的流操作
3.1 Collect(toList())
Collect(toList())方法由Stream里的值生成一个列表,是一个及早求值操作。Stream的of方法使用一组初始值生成新的Stream。使用的实例如下:
private static void collectTest() {
List<String> collected = Stream.of("a","b","c").collect(Collectors.toList());
System.out.println(collected);
}
该实例展示了Stream操作的通用格式,首先由列表生成一个Stream,然后进行一些Stream上的操作,继而是collect操作,由Stream生成列表。
3.2 map
如果有一个函数可以将一种类型的值转换成另外一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。 注意:该map并不是Java中的Map数据结构,不是键值对,不要混淆了。
private static void mapTest() {
List<String> collectedMap = Stream.of("a", "b", "c").map(string ->
string.toUpperCase()).collect(Collectors.toList());
System.out.println(collectedMap);
}
3.3 filter
遍历数据并检查其中的元素时,可以尝试使用Stream中提供的新方法filter:
private static void filterTest() {
List<Integer> filterList = Stream.of(1, 7, 9, 4, 3, 5).filter(value -> value >= 5).collect(Collectors.toList());
}
filter操作可以看做if条件语句。
3.4 flatMap
flatMap方法可以用Stream替换值,然后将多个Stream连接成一个Stream。map操作可用一个新的值代替Stream中的值,但有时用户希望让map操作 有点变化,生成一个新的Stream对象取而代之。用户通常不希望结果是一连串的流,此时flatMap最能派上用场。
private static void flatMapTest() {
Integer a[] = {1, 2, 3};
Integer b[] = {7, 8, 9};
List<Integer> flatMap = Stream.of(a, b).flatMap(numbers -> Arrays.stream(numbers)).collect(Collectors.toList());
}
3.5 max和min
Stream上常用的操作之一是求最大值和最小值:
private static void maxAge() {
StudentDTO maxId = studentDTOList.stream().max(Comparator.comparing(student -> student.getId())).get();
System.out.println(maxId);
}
java8提供了一个新的静态方法comparing,使用它可以方便的实现一个比较器。
max和min操作都属于更通用的一种编程模式。该模式的实现样板式代码如下:
Object accumulator = initialValue;
for (Object element:collection) {
accumulator = combine(acctmulator,element);
}
首先赋给accumulator一个初始值initialValue,然后在循环体中,通过调用combine函数,拿accumulator和集合中的每一个元素做运算, 再将运算结果赋给accumulator,最后accumulator的值就是想要的结果。
3.6 reduce
reduce操作可以实现从一组值中生成一个值,在上面中提到的count,min和max方法,因为常用而被纳入标准库中。事实上,这些方法都是reduce操作。下图展示了如何通过reduce操作对Stream中的数字之和,以0作为起点——一个空Stream的求和结果,每一步都将Stream中的元素累加到accumulator,遍历至Stream中的最后一个元素时,accumulator的值就是所有元素的和。
Lambda表达式就是reducer,它执行求和操作,有两个参数:传入Stream中的当前元素和acc。将两个参数相加,acc是累加器,保存着当前累加结果。 Lambda表达式的返回值是最新的acc,是上一轮acc的值和当前元素相加的结果。另一个参数0表示累加结果acc的初始值。
实现的代码如下:
private static void reduceTest() {
int sum = Stream.of(1, 2, 3, 4, 5).reduce(0, (acc, element) -> acc + element);
}
4.高阶函数
高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数。因此如果函数的参数列表中包含函数接口,或该函数返回一个函数接口,那么该函数就是高阶函数。map操作是一个高阶函数,因为它的mapper参数是一个函数,事实上上面介绍的Stream接口中,几乎所有的函数都是高阶函数。
5.总结
- 内部迭代将更多控制权交给了集合类。
- 和 Iterator 类似, Stream 是一种内部迭代方式。
- 将 Lambda 表达式和 Stream 上的方法结合起来, 可以完成很多常见的集合操作。