我正在参加「掘金·启航计划」
在平常的开发中,经常会对数组,集合进行遍历,操作其中的元素,通常会使用for循环的方式进行处理,这样处理会让代码看起来比较繁杂,不够简洁。而在jdk 8中引入了Stream流,使用Stream流的方式进行操作将会让代码变得更加简洁,优雅。本文就Stream 流做简单的介绍,结合日常开发中的常用操作介绍下api的使用。
1.Stream流是什么?
stream类的注释中有着这样一句话
A sequence of elements supporting sequential and parallel aggregate operations.
翻译过来就是:支持顺序和并行聚合操作的元素序列。 这样一看,似乎不太明白,通俗的讲,就是将一些数据源(集合,数组等)进行一列操作(过滤,求和等)得到我们想要的结果。类似于工厂的流水线操作,将原材料经过一系列的加工和处理,得到最后的成品。一个Stream管道流主要就包括三部分:管道流的创建,管道流的处理,获取流处理结果。
2.Stream流的使用
2.1Stream流的创建
一般我们会根据已有的数组、集合等直接创建出新的Stream流。
2.1.1stream()
创建一个串行流
List<Person> list = new ArrayList<>();
list.add(new Person("玄武1",32,1,"四川绵阳"));
list.add(new Person("玄武2",33,2,"四川成都"));
list.add(new Person("玄武3",12,1,"四川成都"));
list.add(new Person("玄武4",23,2,"四川德阳"));
Stream<Person> stream = list.stream();
2.1.2parallelStream()
创建一个并行流
Stream<Person> personStream = list.parallelStream();
2.1.3Stream.of()
基于给定的元素创建一个串行流
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
注:Stream.of()方法是一个静态方法,参数是一个可变参数类型,返回值是一个Stream 流对象,并且需要指定泛型类型。例子:
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7);
2.2Stream流的中间操作
准备需要的数据源:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private int age;
private int sex;
private String address;
}
List<Person> list = new ArrayList<>();
list.add(new Person("玄武1",32,1,"四川绵阳"));
list.add(new Person("玄武2",33,2,"四川成都"));
list.add(new Person("玄武3",12,1,"四川成都"));
list.add(new Person("玄武4",23,2,"四川德阳"));
list.add(new Person("玄武5",43,2,"湖北武汉"));
list.add(new Person("玄武6",12,1,"四川阿坝"));
list.add(new Person("玄武7",54,2,"重庆市"));
list.add(new Person("玄武8",23,1,"四川德阳"));
list.add(new Person("玄武9",32,2,"四川成都"));
list.add(new Person("玄武10",33,1,"四川成都"));
2.2.1filter
作用:按照条件过滤符合要求的元素, 返回新的stream流注意:下面我们每一个操作我们都打印看看数据
// filter 过滤姓名是玄武1的数据
stream.filter(s -> "玄武1".equals(s.getName())).forEach(System.out::println);
输出:
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
有的人说使用stream是挺方便的,但是没有for循环那么容易debug呀,不急,在idea 中有专门针对stream流的调试工具我们点进去看看
这里就很清晰地看见我们的数据在流中是如何变化的,这个还不太明显,当我们的中间过程有几个 的时候,我们就可以很方便的看出数据在哪一步变成什么样子了。后面再说
2.2.2map
将已有的元素转换为另一个对象类型,一对一逻辑,返回新的stream流
// map 输出所有的地址
stream.map(s->s.getAddress()).forEach(System.out::println);
输出:
四川绵阳
四川成都
四川成都
四川德阳
湖北武汉
四川阿坝
重庆市
四川德阳
四川成都
四川成都
看看调试工具,是不是一目了然,体会下map的一对一
2.2.3flatMap
相对于map来说多了个flat(平铺,扁平化),如何理解呢?我们看这样一个例子
List<Person> list = new ArrayList<>();
list.add(new Person("玄武1",32,1,"四川绵阳"));
list.add(new Person("玄武2",33,2,"四川成都"));
list.add(new Person("玄武3",12,1,"四川成都"));
list.add(new Person("玄武4",23,2,"四川德阳"));
list.add(new Person("玄武5",43,2,"湖北武汉"));
list.add(new Person("玄武6",12,1,"四川阿坝"));
list.add(new Person("玄武7",54,2,"重庆市"));
list.add(new Person("玄武8",23,1,"四川德阳"));
list.add(new Person("玄武9",32,2,"四川成都"));
list.add(new Person("玄武10",33,1,"四川成都"));
List<List<Person>> allList = new ArrayList<>();
allList.add(list);
// 增加一个list
List<Person> list2 = new ArrayList<>();
list2.add(new Person("玄武11", 23, 1, "四川成都"));
allList.add(list2);
// map
System.out.println("------------map---------------");
List<Stream<Person>> collect = allList.stream().map(s -> s.stream()).collect(Collectors.toList());
collect.forEach(System.out::println);
// flatmap
System.out.println("----------flatmap-----------");
List<Person> collect1 = allList.stream().flatMap(s -> s.stream()).collect(Collectors.toList());
collect1.forEach(System.out::println);
输出:
------------map---------------
java.util.stream.ReferencePipeline$Head@2aaf7cc2
java.util.stream.ReferencePipeline$Head@6e3c1e69
----------flatmap-----------
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
Person(name=玄武2, age=33, sex=2, address=四川成都)
Person(name=玄武3, age=12, sex=1, address=四川成都)
Person(name=玄武4, age=23, sex=2, address=四川德阳)
Person(name=玄武5, age=43, sex=2, address=湖北武汉)
Person(name=玄武6, age=12, sex=1, address=四川阿坝)
Person(name=玄武7, age=54, sex=2, address=重庆市)
Person(name=玄武8, age=23, sex=1, address=四川德阳)
Person(name=玄武9, age=32, sex=2, address=四川成都)
Person(name=玄武10, age=33, sex=1, address=四川成都)
Person(name=玄武11, age=23, sex=1, address=四川成都)
map输出的是两个list流元素,而使用flatMap后,会先把两个list流中的元素全部平铺合并成一个流,包含了11个Person对象,所以输出了每个元素。
两者区别:map 对流元素进行转换,而flatMap 是先对流中的每个元素平铺后又转换成为 Stream 流。
2.2.4limit
仅保留集合前面指定个数的元素,返回新的stream流
// limit 输出前两个人员的姓名和地址
stream.limit(2).forEach(s -> System.out.println("姓名:" + s.getName() + ",地址:" + s.getAddress()));
输出:
姓名:玄武1,地址:四川绵阳
姓名:玄武2,地址:四川成都
注:这里的limit参数从1开始
2.2.5skip()
跳过集合前面指定个数的元素,返回新的stream流,对比limit,这个也不难理解
// skip 跳过前三个输出后面的数据
stream.skip(3).forEach(System.out::println);
输出:
Person(name=玄武4, age=23, sex=2, address=四川德阳)
Person(name=玄武5, age=43, sex=2, address=湖北武汉)
Person(name=玄武6, age=12, sex=1, address=四川阿坝)
Person(name=玄武7, age=54, sex=2, address=重庆市)
Person(name=玄武8, age=23, sex=1, address=四川德阳)
Person(name=玄武9, age=32, sex=2, address=四川成都)
Person(name=玄武10, age=33, sex=1, address=四川成都)
2.2.6distinct()
对所有元素进行去重,返回新的stream流
// distinct 去重
// 加入一个相同的数据
list.add(new Person("玄武10", 33, 1, "四川成都"));
stream.distinct().forEach(System.out::println);
输出:
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
Person(name=玄武2, age=33, sex=2, address=四川成都)
Person(name=玄武3, age=12, sex=1, address=四川成都)
Person(name=玄武4, age=23, sex=2, address=四川德阳)
Person(name=玄武5, age=43, sex=2, address=湖北武汉)
Person(name=玄武6, age=12, sex=1, address=四川阿坝)
Person(name=玄武7, age=54, sex=2, address=重庆市)
Person(name=玄武8, age=23, sex=1, address=四川德阳)
Person(name=玄武9, age=32, sex=2, address=四川成都)
Person(name=玄武10, age=33, sex=1, address=四川成都)
调试可以看出,两条同样的数据最后只取一条
2.2.7sorted()
对所有的元素按照指定规则进行排序,返回新的stream流
//sorted 按照年龄排序
stream.sorted(Comparator.comparingInt(Person::getAge)).forEach(System.out::println);
输出:
Person(name=玄武3, age=12, sex=1, address=四川成都)
Person(name=玄武6, age=12, sex=1, address=四川阿坝)
Person(name=玄武4, age=23, sex=2, address=四川德阳)
Person(name=玄武8, age=23, sex=1, address=四川德阳)
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
Person(name=玄武9, age=32, sex=2, address=四川成都)
Person(name=玄武2, age=33, sex=2, address=四川成都)
Person(name=玄武10, age=33, sex=1, address=四川成都)
Person(name=玄武5, age=43, sex=2, address=湖北武汉)
Person(name=玄武7, age=54, sex=2, address=重庆市)
2.2.8peek()
对流中的每个元素进行逐个遍历处理,返回处理后的stream流,官方对这个的解释是为了方便调试用的,就是在 每个阶段通过peek操作打印出当前数据的情况。
stream.peek(System.out::println).map(s->"玄武1".equals(s.getName())).forEach(System.out::println);
输出:
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
true
Person(name=玄武2, age=33, sex=2, address=四川成都)
false
Person(name=玄武3, age=12, sex=1, address=四川成都)
false
Person(name=玄武4, age=23, sex=2, address=四川德阳)
false
Person(name=玄武5, age=43, sex=2, address=湖北武汉)
false
Person(name=玄武6, age=12, sex=1, address=四川阿坝)
false
Person(name=玄武7, age=54, sex=2, address=重庆市)
false
Person(name=玄武8, age=23, sex=1, address=四川德阳)
false
Person(name=玄武9, age=32, sex=2, address=四川成都)
false
Person(name=玄武10, age=33, sex=1, address=四川成都)
false
感觉没啥用。。。
总结:
| 操作 | 作用 |
|---|---|
| filter() | 按照条件过滤符合要求的元素, 返回新的stream流 |
| map() | 将已有的元素转换为另一个对象类型,一对一逻辑,返回新的stream流 |
| flatMap() | 将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流 |
| limit() | 仅保留集合前面指定个数的元素,返回新的stream流 |
| skip() | 跳过集合前面指定个数的元素,返回新的stream流 |
| distinct() | 对Stream中所有元素进行去重,返回新的stream流 |
| sorted() | 对stream中所有的元素按照指定规则进行排序,返回新的stream流 |
| peek() | 对stream流中的每个元素进行逐个遍历处理,返回处理后的stream流 |
注:对stream 的任何修改都不会修改它的数据源
2.3Stream流的终止操作
2.3.1foreach()
无返回值,对元素进行逐个遍历
// forEach
stream.forEach(System.out::println);
输出:
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
Person(name=玄武2, age=33, sex=2, address=四川成都)
Person(name=玄武3, age=12, sex=1, address=四川成都)
Person(name=玄武4, age=23, sex=2, address=四川德阳)
Person(name=玄武5, age=43, sex=2, address=湖北武汉)
Person(name=玄武6, age=12, sex=1, address=四川阿坝)
Person(name=玄武7, age=54, sex=2, address=重庆市)
Person(name=玄武8, age=23, sex=1, address=四川德阳)
Person(name=玄武9, age=32, sex=2, address=四川成都)
Person(name=玄武10, age=33, sex=1, address=四川成都)
2.3.2count(),max(),min()
count()返回stream处理后最终的元素个数,max()返回元素最大值,min()返回元素最小值
// 获取年龄大于20的数量
System.out.println(stream.filter(s -> s.getAge() > 20).count());
输出:8
// 先获取年龄大于20的人员,取其年龄最大的数据
System.out.println(stream.filter(s -> s.getAge() > 20).max(Comparator.comparingInt(Person::getAge)).get());
输出:
Person(name=玄武7, age=54, sex=2, address=重庆市)
// 先获取年龄大于20的人员,取其年龄最小的数据
System.out.println(stream.filter(s -> s.getAge() > 20).min(Comparator.comparingInt(Person::getAge)).get());
输出:
Person(name=玄武4, age=23, sex=2, address=四川德阳)
2.3.5findFirst()、findAny()、anyMatch()、allMatch()、noneMatch()
- findFirst():找到第一个符合条件的元素时则终止流处理
Person person = list.stream().findFirst().get();
System.out.println(person);
输出:
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
- findAny():找到任何一个符合条件的元素时则终止出流处理。
Person person = list.stream().findAny().get();
System.out.println(person);
输出:
Person(name=玄武1, age=32, sex=1, address=四川绵阳)
注:findAny()这个对于串行流时与findFirst相同,对于并行流时比较高效,任何分片中找到都会终止后续计算逻辑。
- anyMatch():返回一个boolean值,类似于isContains() ,用于判断是否有符合条件的元素
boolean b = list.stream().anyMatch(s -> "玄武7".equals(s.getName()));
System.out.println(b);
输出:
true
- allMatch():返回一个boolean值,用于判断是否所有元素都符合条件,全部都符合返回true
boolean result = list.stream().allMatch(s -> "玄武7".equals(s.getName()));
System.out.println(result);
输出:
false
- noneMatch():返回一个boolean值, 用于判断是否所有元素都不符合条件,全部都不符合返回true
boolean result1 = list.stream().allMatch(s -> "玄武7".equals(s.getName()));
System.out.println(result1);
输出:
false
后续将会介绍Stream的其他更加高级的用法,敬请期待!