Stream
是java8的新特性,作用可以看成是sql语句,同样仅仅是数据的搬运工,但是却可以在输出时进行检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等操作- 它的源数据可以是 Collection、Array 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。
- Stream不同于java.io的InputStream和OutputStream,它代表的是任意Java对象的序列。两者对比
java.io
存储 顺序读写的byte或char 用途 序列化至文件或网络java.util.stream
顺序输出的任意Java对象实例 内存计算/业务逻辑
- Stream有如下两种:
- stream 串行流
- parallelStream 并行流,可多线程执行
有同学会问:一个顺序输出的Java对象序列,不就是一个List容器吗?
再次划重点:这个Stream和List也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。如我们可以利
Stream创建后最多只能操作2次,之后关闭无法再使用。如
Stream<Long> stream=Stream.generate(new NatualSupplier()).limit(100);
stream.filter(s -> s % 2 == 0).forEach(System.out::println);
stream.forEach(System.out::println);
第二次foreach就报错:流已经关闭 而limit算一轮,filter.foreach也算一轮
创建Stream
使用Stream.of()静态方法,传入可变参数
- 或者传入数组或者
Collection
构造Stream - list类型可以直接调用
stream()
转为流 - 通过
Supplier
创建Stream。- 基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。
- 通过一些API提供的接口,直接获得Stream
- 如Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:
Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))
- 一个长字符串分割成Stream序列:
Pattern p = Pattern.compile("\\s+");
- 如Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:
Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
Stream<String> stream = Stream.of("A", "B", "C", "D");
Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
Stream<String> stream2 = List.of("X", "Y", "Z").stream();
//---------------------------------无限序列用法
public class Main {
public static void main(String[] args) {
Stream<Integer> natual = Stream.generate(new NatualSupplier());
// 注意:无限序列必须先变成有限序列再打印:
natual.limit(20).forEach(System.out::println); //取20条结果
}
}
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get() {
n++;
return n;
}
}
基本类Stream
因为Java的范型不支持基本类型,所以我们无法用Stream
这样的类型,会发生编译错误。为了保存int,只能使用Stream ,但这样会产生频繁的装箱、拆箱操作。为了提高效率,Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream,它们的使用方法和范型Stream没有大的区别,设计这三个Stream的目的是提高运行效率: 常用方法
Stream可以分为两类,一类是转换操作,即把一个Stream转换为另一个Stream,例如map()和filter(),另一类是聚合操作,即对Stream的每个元素进行计算,得到一个确定的结果,例如reduce()。
- 转换不会触发任何计算,即对调用stream无任何影响,返回一个新stream。而聚合会改变调用者,还会层层获取数据进行计算
下面之所以着重把下面3个方法拿出来讲,是因为排序合并等功能list也有,而下面3个方法,自定义规则进行操作是stream的特点
Stream<Long> s1 = Stream.generate(new NatualSupplier()); Stream<Long> s2 = s1.map(n -> n * n); Stream<Long> s3 = s2.map(n -> n - 1); Stream<Long> s4 = s3.limit(10); s4.reduce(0, (acc, n) -> acc + n); s4向s3请求,s3又向s2请求...
forEach()
-
map()
Stream.map()是Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream。类似于高阶函数,总之就是根据传入的规则,逐个元素进行转换
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5); Stream<Integer> s2 = s.map(n -> n * n);
filter()
所谓filter()操作,就是对一个Stream的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream。
//得到奇数 IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9) .filter(n -> n % 2 != 0) .forEach(System.out::println);
reduce()
reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。
public class Main { public static void main(String[] args) { int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n); System.out.println(sum); // 45 } } // 计算过程: acc = 0 // 初始化为指定值 acc = acc + n = 0 + 1 = 1 // n = 1 acc = acc + n = 1 + 2 = 3 // n = 2 acc = acc + n = 3 + 3 = 6 // n = 3 acc = acc + n = 6 + 4 = 10 // n = 4 acc = acc + n = 10 + 5 = 15 // n = 5 acc = acc + n = 15 + 6 = 21 // n = 6 acc = acc + n = 21 + 7 = 28 // n = 7 acc = acc + n = 28 + 8 = 36 // n = 8 acc = acc + n = 36 + 9 = 45 // n = 9
转其他类型
转Collection
使用collect()可以输出集合对象,参数为Collector实例
- 如
collect(Collectors.toList())
表示转为List,toSet即转set ```java public class Main { public static void main(String[] args) { Streamstream = Stream.of(“Apple”, “”, null, “Pear”, “ “, “Orange”); List list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());
System.out.println(list); } }
- 如
<a name="zq6GI"></a>
## 转数组
- `toArray(String[]::**new**)`
```java
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
转Map
跟转集合差不多,但是需要指定2个映射函数
Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft"); Map<String, String> map = stream .collect(Collectors.toMap( // 把元素s映射为key: s -> s.substring(0, s.indexOf(':')), // 把元素s映射为value: s -> s.substring(s.indexOf(':') + 1))); System.out.println(map);
其他操作
分组输出
分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组,第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List
List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots"); Map<String, List<String>> groups = list.stream() .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList())); System.out.println(groups);
排序
-
去重
-
截取
skip()跳过前?个元素,limit()截取当前stream的前n个元素
List.of("A", "B", "C", "D", "E", "F") .stream() .skip(2) // 跳过A, B .limit(3) // 截取C, D, E .collect(Collectors.toList()); // [C, D, E]
合并
-
集合流内元素合并
```java Stream
- > s = Stream.of(
Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9));
Stream
<a name="xDcEj"></a>
## 并行
- 即多线程处理流(默认是单线程) 只需用方法转并行流即可,我们对并行流的操作后面的多线程处理不需要我们来写,我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。
```java
Stream<String> s = ...
String[] result = s.parallel() // 变成一个可以并行处理的Stream
.sorted() // 可以进行并行排序
.toArray(String[]::new);
其他聚合方法
除了reduce()和collect()外,Stream还有一些常用的聚合方法:
- count():用于返回元素个数;
- max(Comparator<? super T> cp):找出最大元素;
- min(Comparator<? super T> cp):找出最小元素。
针对IntStream、LongStream和DoubleStream,还额外提供了以下聚合方法:
- sum():对所有元素求和;
- average():对所有元素求平均数。
还有一些方法,用来测试Stream的元素是否满足以下条件:
- boolean allMatch(Predicate<? super T>):测试是否所有元素均满足测试条件;
- boolean anyMatch(Predicate<? super T>):测试是否至少有一个元素满足测试条件。
最后一个常用的方法是forEach(),它可以循环处理Stream的每个元素,我们经常传入System.out::println来打印Stream的元素:
Stream