1.使用流的好处
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现,例如你要在集合中筛选一个红色的苹果,你可以用类似于sql式的查询结构来说明你要干什么就可以了,而无需想着如何的去实现它,比如使用for循环+if判断)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
我们将使用下面的代码来说明本章的内容:
/*
* 菜品类
* */
public class Dish {
private final String name; //名字
private final boolean vegetarian; //是否为素菜
private final int calories;//蕴含热量
private final Type type;//菜品种类
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override public String toString() {
return name;
}
public enum Type { MEAT, FISH, OTHER }
}
然后在创建一个菜品列表:
List<Dish> menu = Arrays.asList( new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH) );
我现在有个需求,就是筛选出热量大于400的菜品,按热量大小排序,将菜品名称提取出来形成一个List,在java8之前,我们只能这样做:
List<Dish> heightDishList = new ArrayList<Dish>();
//筛选菜品热量大于400的菜品
for(Dish dish : menu){
if(dish.getCalories()>400){
heightDishList.add(dish);
}
}
//给菜品排序
heightDishList.sort(new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
// TODO Auto-generated method stub
return o1.getCalories()-o2.getCalories();
}
});
//提取菜品名称
List<String> heightDishNameList = new ArrayList<>();
for(Dish dish : heightDishList){
heightDishNameList.add(dish.getName());
}
//打印菜品
for(String string : heightDishNameList){
System.out.println(string);
}
而当我们使用java8的stream,我们就可以这样搞:
List<String> heightDishNameDishs = menu.stream().filter(d1 -> d1.getCalories()>400)//热量大于400
.sorted(Comparator.comparing(Dish::getCalories))//排序
.map(Dish::getName)//提取名称
.collect(Collectors.toList());//形成一个List
//打印
heightDishNameDishs.stream().forEach(System.out::println);
我们发现这样做的好处有:
•代码是以声明性方式写的:说明想要完成什么(筛选热量低的菜肴)而不是说明如何实现一个操作(利用循环和if条件等控制流语句)。你在前面的章节中也看到了,这种方法加上行为参数化让你可以轻松应对变化的需求:你很容易再创建一个代码版本,利用Lambda表达式来筛选高卡路里的菜肴,而用不着去复制粘贴代码。
•你可以把几个基础操作链接起来,来表达复杂的数据处理流水线(在filter后面接上sorted、map和collect操作,如图4-1所示),同时保持代码清晰可读。filter的结果被传给了sorted方法,再传给map方法,最后传给collect方法。
为了利用多核架构并行执行这段代码,你只需要把stream()换成parallelStream():
List<String> heightDishNameDishs = menu.parallelStream().filter(d1 -> d1.getCalories()>400)//热量大于400
.sorted(Comparator.comparing(Dish::getCalories))//排序
.map(Dish::getName)//提取名称
.collect(Collectors.toList());//形成一个List
总结一下,Java 8中的Stream API可以让你写出这样的代码:
•声明性——更简洁,更易读
•可复合——更灵活
•可并行——性能更好
2.流简介
要讨论流,我们先来谈谈集合,这是最容易上手的方式了。Java 8中的集合支持一个新的stream方法,它会返回一个流(接口定义在java.util.stream.Stream里)。你在后面会看到,还有很多其他的方法可以得到流,比如利用数值范围或从I/O资源生成流元素 。
那么什么是流?流就是从支持数据处理操作的源生成的元素序列。这是书上给的定义,简直不知所云,后面给的解释也是不知所云。但我还是根据自己的理解,去尽量理解这些概念性的东西,实在理解不了或者理解错了也无所谓,因为这并影响我们使用:
1.什么是元素序列?对比与java的集合框架是讲究数据的存储与访问,那么流的这个元素序列就是用于表达计算的一种东东。比如你前面见到的filter、sorted和map。
2.什么是源?流会使用一个提供数据的源,如集合、数组或输入/输出资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
3.数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。
此外流还有两个重要的特点:流水线和内部迭代
很多流操作本身会返回一个流(比如filter,map等操作),这样多个操作就可以链接起来,形成一个大的流水线
内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
3.一个流只能被消费一次
4.内部迭代和外部迭代
所谓外部迭代就是我们开篇所举例的那种使用for循环来实现的例子,而内部迭代就是之后使用stream流优化的例子,你会发现使用stream的例子并没有什么显示的迭代,因为它在内部已经帮你做好了。
关于内部迭代的好处,书的作者给了一个有趣的比方:
也就是说stream的内部迭代可以帮助我们并行优化处理数据!
5.流操作
流操作分为两类
一是中间操作:
诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
二是终端操作:
6.使用流
总而言之,流的使用一般包括三件事:
•一个数据源(如集合)来执行一个查询;
•一个中间操作链,形成一条流的流水线;
•一个终端操作,执行流水线,并能生成结果。