流是Java API的新成员,以声明的方式处理数据合集。就现在来说,你可以把它们看成遍历数据集的高级迭代器。可以透明地并行处理,你无需写任何多线程代码了。
一、java7与java8中实现方式对比
我们首先举个例子,看一下在java7和java8当中,引入Stream Api后,究竟发生了哪些变化?
实现一个从菜单筛选低热量(小于500)菜肴名称并从小到大排序的功能:
1.1 java7实现
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @description: 实现一个从菜单筛选低热量(小于500)菜肴名称并从小到大排序的功能
* @author:weirx
* @date:2021/10/20 15:00
* @version:3.0
*/
public class ChoiceCalorieByJava7 {
@Data
static class Dish {
private String name;
private Integer calorie;
public Dish(String name, Integer calorie) {
this.name = name;
this.calorie = calorie;
}
}
public static void main(String[] args) {
List<Dish> dishes = Arrays.asList(
new Dish("烧烤", 1000),
new Dish("沙拉", 100),
new Dish("汉堡", 1200),
new Dish("火锅", 800),
new Dish("粥", 300)
);
List<Dish> lowerCalorieDishes = new ArrayList<>();
// 筛选小于500卡路里
for (Dish dish : dishes) {
if (dish.getCalorie() < 500) {
lowerCalorieDishes.add(dish);
}
}
// 排序
lowerCalorieDishes.sort(new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return o1.getCalorie().compareTo(o2.getCalorie());
}
});
List<String> dishNames = new ArrayList<>();
// 获取菜肴名称
for (Dish dish : lowerCalorieDishes) {
dishNames.add(dish.getName());
}
// 打印结果 [沙拉, 粥]
System.out.println(dishNames);
}
}
1.2 java8实现
import lombok.Data;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description: 实现一个从菜单筛选低热量(小于500)菜肴名称并从小到大排序的功能
* @author:weirx
* @date:2021/10/20 15:00
* @version:3.0
*/
public class ChoiceCalorieByJava8 {
@Data
static class Dish {
private String name;
private Integer calorie;
public Dish(String name, Integer calorie) {
this.name = name;
this.calorie = calorie;
}
}
public static void main(String[] args) {
List<Dish> dishes = Arrays.asList(
new Dish("烧烤", 1000),
new Dish("沙拉", 100),
new Dish("汉堡", 1200),
new Dish("火锅", 800),
new Dish("粥", 300)
);
List<String> dishNames = dishes.stream()
.filter(dish -> dish.getCalorie() < 500) //筛选卡路里小于500
.sorted(Comparator.comparing(Dish::getCalorie)) //排序
.map(Dish::getName) //获取名称
.collect(Collectors.toList()); //转成list
// 打印结果 [沙拉, 粥]
System.out.println(dishNames);
}
}
相比之下在java7中繁杂的业务代码只需要一条流式的代码就完成了,并且将其中的stream()替换成parallelStream()就可以利用多核去执行这行代码。
1.3 总结
下面通过上面的例子,简单总结下使用Stream API的好处:
1)声明性:更简洁,更易读。体现事物本身,而非代码过程。
2)可复合:更灵活,减少大量的重复代码。
3)可并行:更好的性能。
二、如何理解流?
简短的定义就是:从支持数据处理操作的源生成的元素序列。
下面简单介绍一下这句话的含义:
1)元素序列:像集合一样,流也提供一个接口,可以访问特定元素的一组有序值。集合(ArrayList、LinkedList等)关注数据,而流关注的是计算(filter 、 sorted 和 map等)。
2)源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
3)数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执行,也可并行执行。
除此之外还有以下两个特点:
4)流水线:多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。流水线的操作可以看作对数据源进行数据库式查询。
5)内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
结合前面的例子简单说明下:
List<String> dishNames = dishes.stream()
.filter(dish -> dish.getCalorie() < 500) //筛选卡路里小于500
.sorted(Comparator.comparing(Dish::getCalorie)) //排序
.map(Dish::getName) //获取名称
.collect(Collectors.toList()); //转成list
其中dishNames就是源,它给流提供一个元素序列,接下来,对流应用一系列数据处理操作:filter 、 sorted 和 map等。最后, collect 操作开始处理流水线,并返回结果(它和别的操作不一样,因为它返回的不是流,在这里是一个 List。
三、流和集合有哪些不同?
1)集合可以多次遍历,而流只能被遍历一次
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
stream.forEach(System.out::print);
}
如上操作会报出以下异常:
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.cloud.bssp.java8.stream.CollectionAndStream.main(CollectionAndStream.java:19)
2)遍历数据的方式不同
使用 Collection 接口需要用户去做迭代(比如用 foreach ),这称为外部迭代。 相反,Streams库使用内部迭代
相比于外部迭代,内部迭代的优势在于:
内部迭代时,项目可以透明地并行处理,或者用更优化的顺序进行处理。不需要像外部迭代手动去处理并行问题。
![](https://img.haomeiwen.com/i16830368/e4f707139e749522.png)
四、流的操作
Stream API定义了很多的操作,这些操作可以分为两大类:
1)filter、map、limit等可以连成一条流水线。这类操作也可以简称为中间操作。
诸如 filter 或 sorted 等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
2)collect触发流水线执行并关闭它。这类操作被称为终端操作。
终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如 List 、 Integer ,甚至 void 。
总而言之,流的使用一般包括三件事:
1)一个数据源(如集合)来执行一个查询;
2)一个中间操作链,形成一条流的流水线;
3)一个终端操作,执行流水线,并能生成结果
网友评论