Stream流(一)

126 阅读9分钟

我正在参加「掘金·启航计划」

在平常的开发中,经常会对数组,集合进行遍历,操作其中的元素,通常会使用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的其他更加高级的用法,敬请期待!