Stream是Java 8提供的新功能,是对集合(Collection)对象功能的增强,能对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或大批量数据操作 (bulk data operation)。 与Lambda 表达式结合,也可以提高编程效率、简洁性和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
特点
- Stream自己不会存储元素。
- Stream的操作不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。它会等到需要结果的时候才执行。也就是执行终端操作的时候。
图解
一个Stream的操作就如上图,在一个管道内,分为三个步骤,第一步是创建Stream,从集合、数组中获取一个流,第二步是中间操作链,对数据进行处理。第三步是终端操作,用来执行中间操作链,返回结果。
创建 Stream
1 将集合作为数据源,读取集合中的数据到一个流中
// 1. 实例化一个集合
List<Integer> list = new ArrayList<>();
// 2. 填充元素
Collections.addAll(list,0,1,2,3,4,5,6,7,8,9);
// 3. 读取集合中的数据,将其读取到顺序流中
// Stream<Integer> stream = list.stream();
// 3. 读取集合中的数据,将其读取到并行流中
Stream<Integer> stream = list.parallelStream();
// 4. 输出stream
System.out.println(stream);
2 将数组作为数据源,读取数组中的数据到一个流中
// 1. 实例化一个数组
Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// 2. 读取数组中的数据,将其读取到流中
Stream<Integer> stream = Arrays.stream(array);
// 3. 输出stream
System.out.println(stream);
3 使用静态方法Stream.of(),通过显式值创建一个流
Stream<Integer> stream = Stream.of(0, 1, 3, 5, 7, 9);
4 由函数创建,创建无限流
迭代 public static Stream iterate(final T seed, final UnaryOperator f)
生成 public static Stream generate(Supplier s)
Stream.generate(Math::random).limit(5).forEach(System.out::print);
List<Integer> collect = Stream.iterate(0,i -> i + 1).limit(5).collect(Collectors.toList());
注意:使用无限流一定要配合limit截断,不然会无限制创建下去。
创建测试数据源
/**
* 学生类: 存储于集合中的数据的类型
*/
private static class Student implements Comparable<Student> {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
score == student.score &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, score);
}
@Override
public int compareTo(Student o) {
return score - o.score;
}
}
/**
* 读取数据源,将集合中存储的数据读取到数据源中
* @return 流
*/
public static Stream<Student> getDataSource() {
// 1. 实例化一个集合,存Student对象
List<Student> list = new ArrayList<>();
// 2. 添加若干数据
Collections.addAll(list,
new Student("xiaoming",17,90),
new Student("xiaohong",18,100),
new Student("xiaohong",21,100),
new Student("xiaolan",18,90),
new Student("xiaoli",19,68),
new Student("xiaobai",18,72),
new Student("xiaohei",20,39),
new Student("xiaoke",17,93),
new Student("xiaoqing",19,79),
new Student("xiaofei",18,56)
);
// 3. 读取数据源,得到Stream对象
return list.stream();
}
中间操作
中间操作: filter 条件过滤
可以将流中满足指定条件的数据保留,去掉不满足指定条件的数据
// 1. 获取数据源
Stream<Student> dataSource = getDataSource();
// 2. 过滤掉集合中成绩不及格的学生信息
dataSource.filter(s -> s.getScore() >= 60).forEach(System.out::println);
中间操作:distinct
去重,去除流中的重复的数据,这个方法是没有参数的,去重的规则与hashSet相同
- 比较两个对象的hashCode
- 再比较两个对象的equals
// 1. 获取数据源
Stream<Student> dataSource = getDataSource();
// 2. 去重
dataSource.distinct().forEach(System.out::println);
中间操作: sorted
sorted(): 将流中的数据,按照其对应的类实现的Comparable接口提供的比较规则进行排序
sorted(Comparator comparator): 将流中的数据按照参数接口提供的比较规则进行排序
// 1. 获取数据源
Stream<Student> dataSource = getDataSource();
// 2. 对流中的数据进行排序
// dataSource.sorted().forEach(System.out::println);
// 3. 对流中的数据按照自定义的规则进行排序
dataSource.sorted((s1,s2) -> s1.age - s2.age).forEach(System.out::println);
中间操作: limit & skip
limit: 限制,表示截取流中的指定数量的数据(从第 0 开始),丢弃剩余部分
skip: 跳过,表示跳过指定数量的数据,截取剩余部分
// 2. 获取成绩的[3,5]名
dataSource.sorted((s1,s2) -> s2.score - s1.score).distinct()
.limit(5)
.skip(2)
.forEach(System.out::println);
中间操作: map
元素映射,提供一个映射规则,将流中的每一个元素替换成指定的元素
map: 对流中的数据进行映射,用新的数据替换旧的数据
// 1. 获取数据源
Stream<Student> dataSource = getDataSource();
// 2. 实际需求: 获取所有学生的名字
// dataSource.map(Student::getName).forEach(System.out::println);
// dataSource.map(s -> s.getName()).forEach(System.out::println);
// 3. 实际需求: 获取所有学生的成绩
// 这里,会得到 Stream<Integer>
// dataSource.map(Student::getScore).forEach(System.out::println);
// 4. 实际需求: 获取所有的学生成绩
IntSummaryStatistics intSummaryStatistics = dataSource.mapToInt(Student::getScore).summaryStatistics();
System.out.println(intSummaryStatistics.getMax());
System.out.println(intSummaryStatistics.getMin());
System.out.println(intSummaryStatistics.getAverage());
中间操作:flatMap
扁平化映射: 一般是用在map映射完成后,流中的数据是一个容器,而我们需要再对容器中的数据进行处理,此时使用扁平化映射,可以将流中的容器中的数据,直接读取到流中
// 1. 实例化一个字符串数组
String[] s = new String[]{ "hello","world"};
// 2. 将字符串数组中的数据读取到流中
Stream<String> stream = Arrays.stream(s);
// 3. 需求: 统计字符串数组中所有出现的字符
stream.map(e -> e.split(""))
.flatMap(Arrays::stream)
.distinct()
.forEach(System.out::print);
output:
helowrd
最终操作
数据准备
public static Stream<Integer> getDataSource() {
// 1. 准备一个容器
List<Integer> datasource = new ArrayList<>();
// 2. 向数据中添加数据
Collections.addAll(datasource,0,1,2,3,4,5,6,7,8,9);
// 3. 读取数据源中的数据,得到Stream对象并返回
return datasource.stream();
}
最终操作:collect
将流中的数据整合起来,最常见的处理是:读取流中的数据, 整合到一个容器中,得到一个集合最终操作过后,会关闭流,
// 1. 获取数据源
Stream<Integer> dataSource = getDataSource();
// 2. 流中的数据的聚合
// List<Integer> collect = dataSource.collect(Collectors.toList());
// Set<Integer> collect = dataSource.collect(Collectors.toSet());
// Map<String, Integer> collect = dataSource.collect(Collectors.toMap(i -> i.toString(), i -> i));
Map<Integer, String> collect = dataSource.collect(Collectors.toMap(i -> i+1, i -> i.toString()));
output:
{1=0, 2=1, 3=2, 4=3, 5=4, 6=5, 7=6, 8=7, 9=8, 10=9}
最终操作 reduce
将流中的数据按照一定的规则聚合起来
// 1. 获取数据源
Stream<Integer> dataSource = getDataSource();
// 2. 最终操作
// Integer sum = dataSource.reduce((p1, p2) -> p1 + p2).get();
// Integer sum = dataSource.reduce(Integer::sum).get(); //使用了方法引用,效果同上
// 这是一个java 8 之前复杂的匿名内部类写法,效果同上
Integer sum = dataSource.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
}).get();
// 3. 输出结果
System.out.println(sum);
output:
45
最终操作 count
统计流中的数据数量
// 1. 获取数据源
Stream<Integer> dataSource = getDataSource();
// 2. 获取流中的数据数量
long count = dataSource.count();
System.out.println(count);
output:
10
最终操作: forEach
遍历流中数据
// 1. 获取数据源
Stream<Integer> dataSource = getDataSource();
// 2. 遍历源中数据
dataSource.forEach(System.out::println);
最终操作: max & min
max: 按照指定的对象比较规则,进行大小的比较,得出流中最大的数据
min: 按照指定的对象比较规则,进行大小的比较,得出流中最小的数据
// 1. 获取数据源
Stream<Integer> dataSource = getDataSource();
// 2. 获取流的最大值
Integer integer = dataSource.max(Integer::compareTo).get();
// 获取流的最小值
// Integer integer = dataSource.min(Integer::compareTo).get();
// 3. 输出结果
System.out.println(integer);
最终操作: allMatch & anyMatch & noneMatch
allMatch: 只有当流中所有的元素都匹配指定的规则,才会返回true
anyMatch: 只要流中的任意数据满足指定的规则,就会返回true
noneMatch: 只有当流中所有的元素都不满足指定的规则,才会返回true
// 1. 获取数据源
Stream<Integer> dataSource = getDataSource();
// 2. 匹配的操作
//所有数据都大于0则返回true,否则返回false
// boolean b = dataSource.allMatch(e -> e > 0);
//存在大于8的数据则返回true,否则返回false
// boolean b = dataSource.anyMatch(e -> e > 8);
//没有一条数据大于9(即所有都小于9)则返回true,否则返回false
boolean b = dataSource.noneMatch(e -> e > 9);
// 3. 输出匹配的结果
System.out.println(b);
最终操作: findFirst & findAny
findFirst: 获取流中的一个元素,获取的是流中的首元素
findAny: 获取流中的一个元素,通常是首元素,但在并行流中,获取的可能不是首元素
注意:
findFirst: 在进行元素获取的时候,无论是串行流还是并行流,获取的都是首元素
findAny: 在进行元素获取的时候,串行流一定获取到的是流中的首元素,并行流获取到的可能是首元素,也可能不是
// 1. 实例化一个集合
ArrayList<Integer> datasource = new ArrayList<>();
Collections.addAll(datasource,0,1,2,3,4,5);
// 2. findFirst演示
//parallelStream() 获取的是并行流
// Integer integer = datasource.parallelStream().findFirst().get();
// System.out.println(integer);
// 3. findAny 演示
Integer integer = datasource.parallelStream().findAny().get();
最终操作 IntStream
// 1. 准备一个int数组,作为数据源
int[] array = new int[] {0,1,2,3,4,5};
// 2. 读取数据到流中,获取IntStream对象
IntStream stream = Arrays.stream(array);
// 3. 常见的最终操作
//System.out.println(stream.max().getAsInt()); // 获取最大值
// System.out.println(stream.min().getAsInt()); // 获取最小值
// sum()不是返回OptionalInt 是因为sum() 不用做空流(即流中没数据)的处理,
// 为空返回 0 就好了,而max() 与 min() 不行
// System.out.println(stream.sum()); // 获取数据和
// System.out.println(stream.count()); // 获取流中的数据的数量
// System.out.println(stream.average().getAsDouble()); // 获取流中的数据的平均值
// 4. 获取一个对流中数据的分析结果
IntSummaryStatistics intSummaryStatistics = stream.summaryStatistics();
System.out.println("max = " + intSummaryStatistics.getMax()); // 获取最大值
System.out.println("min = " + intSummaryStatistics.getMin()); // 获取最小值
System.out.println("sum = " + intSummaryStatistics.getSum()); // 获取数据和
System.out.println("average = " + intSummaryStatistics.getAverage()); // 获取平均值
System.out.println("count = " + intSummaryStatistics.getCount()); // 获取数据的数量