目录
关于Stream
流是java8中加入的api,他是一种处理数据的方式,在这之前我们通常都用集合来处理以及存储数据,在给出流这一api之后,集合和流之间有什么关系或者有什么不同呢?
实例对比
首先通过一个例子可以了解这两者之间的关系,这里我使用的是《Java8实战》这本书中的例子:
有这样一个菜单,上面记录了各种菜的信息。现在我们想通过一系列操作选出热量低于400的三种菜的名称并按照卡路里进行排序,它的一些详细的代码如下:
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<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish d:menu) {
if (d.getCalories() < 400){
lowCaloricDishes.add(d);
}
}
//对对象按照卡路里进行排序
Collections.sort(lowCaloricDishes,new Comparator<Dish>(){
@Override
public int compare(Dish o1, Dish o2) {
return Integer.compare(o1.getCalories(),o2.getCalories());
}
});
//获取每一个对象的name
List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish e:lowCaloricDishes) {
lowCaloricDishesName.add(e.getName());
}
//控制台输出看一下
System.out.println(lowCaloricDishesName);
[season fruit, prawns, rice]
//使用流解决问题
List<String> lowCaloricDishesName = menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
//控制台输出看一下
System.out.println(lowCaloricDishesName);
[season fruit, prawns, rice]
Stream与集合的对比
通过上面一个简单的例子我们可以很容易发现,流的使用会大大简化我们筛选目标的代码。而对于流与集合之间有哪些不同,我做了一下简单的总结:
部分缓存与全部下载
我用一个图解释一下这两者之间的区别:
集合中的每一次操作都伴随着一个确切可操作的集合诞生
而流操作不需要每一步操作都产生一个集合
但是流操作中
这就好比我们在视频网站上看视频
Java8之前的方式就像是把整个视频下下来之后才能播放
而Java8之后引入流之后就像是把视频先缓存一部分就开始播放,不用全部下载。
流使用中需要注意的地方
我们观察之前的流的代码
//使用流解决问题
List<String> lowCaloricDishesName = menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
//控制台输出看一下
System.out.println(lowCaloricDishesName);
流的方法之所以可以连起来,是因为有一些方法输入的参数和输出的参数都是流,就使得整个语句可以连起来
通过一些简单了解我们可以知道,流的方法分为两种,一种是中间操作一种是终端操作
//使用流解决问题
List<String> lowCaloricDishesName = menu.stream()
.filter(d -> {
System.out.println("filtering " + d.getName());
return d.getCalories() < 400;
})
.map(d -> {
System.out.println("mapping " + d);
return d.getName();
})
.limit(2)
.collect(toList());
//控制台输出看一下
System.out.println(lowCaloricDishesName);
对比之前的流操作 换了一个limit操作,这个操作会限制最终得到结果的数量。我们可以先看看结果
filtering pork
filtering beef
filtering chicken
filtering french fries
filtering rice
mapping rice
filtering season fruit
mapping season fruit
[rice, season fruit]
这个limit操作拥有短路特性,如果遇见满足最终输出条件的数据会提前截止语句的运行
并且很容易发现,filter语句遇见满足条件的数据时,会接着运行map语句
这是流中间操作的一些特性
与中间操作不同,终端操作往往是最后一个语句,比如上面的collect方法就是终端操作
这种操作的特点在于,输入参数是流输出参数为不是流的任何值,作为流语句的结束
流数据只能使用一次,如果进行了终端操作之后在使用这个流会报错:
Stream<Dish> s = menu.stream();
s.forEach(System.out::println);//68行
s.forEach(System.out::println);//69行
结果:
pork
beef
chicken
french fries
rice
season fruit
pizza
prawns
salmon
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
at Test.main(Test.java:69)
迭代方式分为外部迭代与内部迭代
for-each与迭代器都是属于外部迭代,这种迭代方式需要获取每一个元素,并执行操作,而对比流操作中的内部迭代,它不用获取每一个元素只用写出需要执行的操作就行了。
这样的代码会更简洁并且并行性更好。
本篇博客的例子内容都是从《Java8实战》中找到的。