Stream流
介绍
Stream流是一种数据渠道,是用于操作数据源(集合,数组等)生成的元素序列。Stream主要用于对于集合迭代器的增强,使之能够完成更加高效的聚合操作(例如:过滤,排序,统计分组等等),或者大批量数据的操作。此外Stream流和lambda表达式的结合使用可以大大增强编码的效率,可读性很强。
注意:
- 流是一次性的,它自己不会存储元素
- 流不会改变源对象,相反,他们会返回一个持有结果的新的Strean流
- 流操作时延迟执行的,这就意味着他们会等到需要结果的时候才去执行
- 流不可以重复使用
并行流和串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel() 与sequential() 在并行流与顺序流之间进行切换。
创建流的三种方式
public class CreateStream {
public static void main(String[] args) {
//第一种方式:使用集合对象创建流
List<Integer> list = Arrays.asList(1, 2, 3);
Stream<Integer> stream1 = list.stream();
//第二种方式:使用数组创建流 Arrays.stream()将数组转成一个流
IntStream stream2 = Arrays.stream(new int[]{1, 2, 3});
//第三种方式:使用Stream.of(),底层还是采用了Arrays.stream()
Stream<Integer> stream = Stream.of(1, 2, 3);
}
}
除了传统的流,还有两种比较特殊的流:
- 空流: Stream.empty()
- 无限流:Stream.generate()和Stream.iterator()。可以配合limit进行使用,限制流的数量
// 接受一个 Supplier 作为参数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
// 初始值是 0,新值是前一个元素值 + 2
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
总结来说:
在Java8中的Collection接口中已经扩展了两种获取流的方式:
stream(): 返回一个顺序流
parallelStream(): 返回一个并行流
同时Java8中的Arrays的静态方法stream()可以获取数组流:
static < Stream<T> stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
我们还可以显示化的使用静态方法创建一个流:
public static<T> Stream<T> of(T... values)values): 返回一个流
利用静态方法Stream.iterate()和Stream.generate()来创建无线流:
迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
生成
public static<T> Stream<T> generate(Supplier<T> s):
流的特性
- 不存储数据
- 不会改变数据源
- 不可以重复使用
我们通过代码进行演示:
1. Steam流的简单使用:
@Test
public void test01() {
List<Integer> list = Arrays.asList(1, 6, 3, 5, 2, 4);
List<Integer> collect = list.stream().filter(item -> item > 3).sorted().collect(Collectors.toList());
System.out.println(collect);
}
输出结果:
[4, 5, 6]
2. Stream流不会改变数据源:
ublic void test02() {
List<Integer> list = Arrays.asList(1, 6, 3, 5, 2, 4);
List<Integer> collect = list.stream().filter(item -> item > 3).sorted().collect(Collectors.toList());
//打印源数据
System.out.println(list);
}
输出结果:
[1, 6, 3, 5, 2, 4]
3. 流不可以重复使用
@Test
public void test03() {
List<Integer> list = Arrays.asList(1, 6, 3, 5, 2, 4);
Stream<Integer> stream = list.stream();
Stream<Integer> newStream = stream.filter(item -> item > 3);
stream.skip(1);
}
输出结果:
java.lang.IllegalStateException: stream has already been operated upon or closed
Stream流的操作类型
第一步:创建流(Stream)
一个数据源,例如:集合,数组等等,获取一个流
第二步:中间操作
一个中间操作链,数据源的数据进行处理
第三步:终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
steam流的大致执行流程即如下所示:
Stream流的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发了终止操作,否则中间操作不会执行任何处理!,则在终止操作时一次性全部处理,称为“惰性求值”。
筛选和切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成的元素的hashCode()和equals()去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定元素 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空气流,与limit(n)互补 |
映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream 。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream 。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream 。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按照自然顺序排序 |
sorted(Comparator comp) | 产生一个新流,其中按照比较器的顺序排序 |
Stream流的终止操作
查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配的元素 |
findFirst() | 返回第一个元素 |
finAny() | 返回当前流中的任意元素 |
count() | 返回流中的元素总数 |
max(Comparator c) | 返回流中的最大值 |
min(Comparator c) | 返回流中的最小值 |
forEach(Consumer c) | 内部迭代 |
归约
方法 | 描述 |
---|---|
reduce(T iden,BinaryOperator b) | 可以将流中的元素反复结合起来,得到一个值,返回T |
anyMatch(Predicate p) | 可以将流中的元素反复结合起来得到一个值,返回Optional |
备注:因为map和reduce的连接通常称为map-reduce模式
收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector 接口的实现,用于给Stream 中元素做汇总的方法 |
Stream流的案例1
给定一个Apple苹果类:
package com.feng.stream;
/**
* Created by FengBin on 2021/8/13 14:21
*/
public class Apple {
private int id;
private String color;
private int weight;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Apple(int id, String color, int weight, String address) {
this.id = id;
this.color = color;
this.weight = weight;
this.address = address;
}
@Override
public String toString() {
return "Apple{" +
"id=" + id +
", color='" + color + '\'' +
", weight=" + weight +
", address='" + address + '\'' +
'}';
}
}
求出不同颜色的苹果的平均重量
private static final List<Apple> appleList = Arrays.asList(
new Apple(1,"red",500,"烟台"),
new Apple(1,"red",300,"杭州"),
new Apple(1,"green",500,"南京"),
new Apple(1,"green",200,"北京")
);
@Test
public void test01() {
appleList.stream().collect(Collectors.groupingBy(item -> item.getColor(), //基于颜色进行分类
Collectors.averagingInt(item -> item.getWeight()))) //统计平均重量
.forEach((k,v) -> {
System.out.println(k + ": " + v);
});
}
输出结果:
red: 400.0
green: 350.0
上一个节点对下一个节点的影响
@Test
public void test02() {
appleList.stream()
.filter(item -> item.getColor().equals("red"))
.peek(item -> System.out.println("第一层筛选:" + item))
.filter(item -> item.getWeight() == 500)
.peek(item -> System.out.println("第二层筛选:" + item))
.collect(Collectors.toList());
}
输出结果:
第一层筛选:Apple{id=1, color='red', weight=500, address='烟台'}
第二层筛选:Apple{id=1, color='red', weight=500, address='烟台'}
第一层筛选:Apple{id=1, color='red', weight=300, address='杭州'}
通过输出也可以看到节点是一个接着一个通过流的,而不是一起通过的…
Stream流的案例2
当前存在一个数组nums,我们要求记录该数组中每一个数组出现的次数,最后对应元素出现的次数按照次数从大到小进行排序
我们可以使用map,key为数组中的数字,value为对应出现的次数,然后将value值按照从大到小排序即可
@Test
public void test03() {
int[] nums = {1,2,3,3,4,4,5,5,5,6,7,8,8,8,8,8,9,10};
Map<Integer,Integer> map = new LinkedHashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
}
Map<Integer, Integer> collect = map.entrySet().stream()
.sorted((entry1, entry2) -> entry2.getValue() - entry1.getValue())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(oldValue,newValue) -> oldValue,LinkedHashMap::new));
collect.forEach((k,v) -> {
System.out.println(k + ": " + v);
});
}
输出结果:
8: 5
5: 3
3: 2
4: 2
1: 1
2: 1
6: 1
7: 1
9: 1
10: 1
.sorted((entry1, entry2) -> entry2.getValue() - entry1.getValue())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(oldValue,newValue) -> oldValue,LinkedHashMap::new));
collect.forEach((k,v) -> {
System.out.println(k + ": " + v);
});
}
输出结果:
```java
8: 5
5: 3
3: 2
4: 2
1: 1
2: 1
6: 1
7: 1
9: 1
10: 1