前言
上一篇博客中针对Stream流的创建做了一个简单总结,这一篇博客就简单总结一下Stream流的操作。在《Java 8 in action》一书中对Stream流的操作总结成两大类——中间操作和终端操作。
中间操作
中间操作——例如filter,sorted等即为中间操作,这些操作会返回一个流,这些流类似于一个流水生产线上的中间操作,这些操作的返回结果就是流。下面开始总结中间操作的一些实例
筛选和切片
filter
这里还是用创建Stream博客中的实例来继续讨论相关问题,这里说的筛选其实就是filter操作,如果我们要选出Dish菜单中的蔬菜,可以直接这样操作。
public static void main(String[] args) {
List<Dish> dishList = DishContainer.getDishList();
//filter中接收的是Predicate接口
List<Dish> result = dishList.stream().filter(Dish::isVegetarian).collect(toList());
System.out.println(result);
}
filter的源码如下所示:
Stream<T> filter(Predicate<? super T> predicate);
其实也就是接受一个Predicate接口,满足要求的作为stream返回,返回类型没有变化,因此为中间操作。
distinct
distinct这个类似数据库中的distinct操作,其实就是去重,下面的实例可以说明distinct操作。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,6,7,7,1);
List<Integer> collect = numbers.stream().filter(i -> i % 2 == 0).distinct().collect(toList());
System.out.println(collect);
}
运行结果:
结果中已经去除了重复的数据。
limit
该方法会返回一个不超过指定长度的流,也类似于SQL语句中的limit操作。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,6,7,7,1);
List<Integer> result = numbers.stream().filter(i -> i % 2 == 0).distinct().limit(2).collect(toList());
System.out.println(result);
}
在上述代码中,加入limit(2),最终返回的结果就只有两个了
skip
skip和limit正好相反。下面的运行结果,与limit的结果互补。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,6,7,7,1);
List<Integer> result = numbers.stream().filter(i -> i % 2 == 0).distinct().skip(2).collect(toList());
System.out.println(result);
}
映射
映射相较于前面的操作,略微有点含量了。
map——对流中每一个元素应用函数
Stream流支持map方法,它会接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素。
例如:我们只需要获取菜单中的所有猜的名字。不需要其他信息,这个时候我们就可以用map来完成。
public static void main(String[] args) {
List<Dish> dishes = DishContainer.getDishList();
//map操作会映射到流中的每个元素上,一定程度上会决定返回值的类型
List<String> dishNames = dishes.stream().map(Dish::getName).collect(toList());
System.out.println(dishNames);
}
flatMap——流的扁平化
一个需求:给定一个单词列表,返回这个单词列表中各不相同的字符。例如,给定单词列表["Hello","World"],你想要返回列表["H","e","l","o","W","r","d"]
这个时候我们首先想到的就是利用map来完成,但是如果利用map(world->world.split("")).distinct()之后,这个返回的是一个Stream<String[]>,但是我们真正需要的是Stream<String>,这个时候可以用flatMap的方式解决它。
public static void main(String[] args) {
String[] strings = {"hello","world"};
List<String> streams = Arrays.stream(strings).map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());
System.out.println(streams);
}
flatMap(Arrays::stream),使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。flatMap其实就是让我们把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。这里还是单独看另外一个实例吧。实例比较简单,一看就懂。
/**
* autor:liman
* createtime:2019/9/1
* comment:flatmapDemo
*/
public class StreamFlagMapDemo {
public static void main(String args[]) {
List<String> teamIndia = Arrays.asList("Virat", "Dhoni", "Jadeja");
List<String> teamAustralia = Arrays.asList("Warner", "Watson", "Smith");
List<String> teamEngland = Arrays.asList("Alex", "Bell", "Broad");
List<String> teamNewZeland = Arrays.asList("Kane", "Nathan", "Vettori");
List<String> teamSouthAfrica = Arrays.asList("AB", "Amla", "Faf");
List<String> teamWestIndies = Arrays.asList("Sammy", "Gayle", "Narine");
List<String> teamSriLanka = Arrays.asList("Mahela", "Sanga", "Dilshan");
List<String> teamPakistan = Arrays.asList("Misbah", "Afridi", "Shehzad");
List<List<String>> playersInWorldCup2016 = new ArrayList<>();
playersInWorldCup2016.add(teamIndia);
playersInWorldCup2016.add(teamAustralia);
playersInWorldCup2016.add(teamEngland);
playersInWorldCup2016.add(teamNewZeland);
playersInWorldCup2016.add(teamSouthAfrica);
playersInWorldCup2016.add(teamWestIndies);
playersInWorldCup2016.add(teamSriLanka);
playersInWorldCup2016.add(teamPakistan);
// 在没有java8 之前,我们针对这个集合需要遍历两次,同时需要一个集合汇总
List<String> listOfAllPlayers = new ArrayList<>();
for(List<String> team : playersInWorldCup2016){
for(String name : team){
listOfAllPlayers.add(name);
}
}
System.out.println("Players playing in world cup 2016");
System.out.println(listOfAllPlayers);
// flatMap会将所有的集合中的集合扁平化
List<String> flatMapList = playersInWorldCup2016.stream()
.flatMap(pList -> pList.stream())
.collect(Collectors.toList());
System.out.println("List of all Players using Java 8");
System.out.println(flatMapList);
}
}
终端操作
终端操作会从流的流水线生成结果,其结果并不是流,例如List、Integer甚至void。从下面开始,就都是终端操作了。
查找和匹配
anyMatch
流中是否有一个元素匹配给定的条件(谓词),只要有一个匹配,就直接返回true。
List<Dish> dishes = DishContainer.getDishList();
boolean hasVegetarian = dishes.stream().anyMatch(Dish::isVegetarian);
if(hasVegetarian){
System.out.println("the menu is vegetarian friendly;");
}
allMatch
看流中所有的元素是否匹配指定的谓词。
boolean isHealthy = dishes.stream().allMatch(d -> d.getCalories() < 1000);
if(isHealthy){
System.out.println("the menu calories is frendly;");
}
noneMatch类似,这里就不再解释了。
findAny与findFirst
findAny——将返回当前流中的任意一个元素,findFirst——出现排序后的第一个元素。
这里直接上实例吧,异常简单。
dishes.stream().filter(Dish::isVegetarian).findFirst().ifPresent(d-> System.out.println(d.getName()));
dishes.stream().filter(Dish::isVegetarian).findAny().ifPresent(d-> System.out.println(d.getName()));
归约
归约一定程度上也是查询操作,这类查询操作需要将流中的所有元素反复结合起来,最终得到一个值。直接上实例吧,真心可以解释的不多。reduce操作,Lambda反复结合每个元素,直到流被归约成一个值。
public class StreamReduceDemo {
public static void main(String[] args) {
//元素求和
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
Integer result = stream.reduce(0, (i, j) -> i + j);
System.out.println(result);
//最大值和最小值
Stream<Integer> numStream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
Optional<Integer> maxNum = numStream.reduce(Integer::max);
System.out.println(maxNum.get());
Stream<Integer> minStream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
Optional<Integer> minNum = minStream.reduce(Integer::min);
System.out.println(minNum.get());
}
}
这里还是贴出一个reduce的详细操作,这个图直接来自《Java8 in action》
总结
总之,区分中间操作和终端操作就是看返回类型是不是一个流,其实区分这两个操作并没有那么重要。只是针对stream中的操作进行了一些简单介绍,下一篇博客会主要总结一个实例。