Stream操作原则上都可以用传统循环实现,本质上是从传统命令式代码改为声明式(业务意图清晰)适合 Stream
- 数据转换、过滤、聚合
- 链式操作清晰表达业务逻辑
- 需要并行处理
- 处理无限序列
- 代码可读性优先
不适合 Stream
- 简单循环(3-5行代码)
- 需要中途 break 多层嵌套
- 性能极端敏感(微秒级)
- 调试困难的环境
- 有检查型异常需要处理
一、构建Stream
-
Stream.of()方法使用
of()方法传入可变参数java import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> stream = Stream.of("A", "B", "C", "D"); // forEach()方法传入符合Consumer接口的void accept(T t)的方法引用: stream.forEach(System.out::println); } } -
基于
Arrays和CollectionCollection(List、Set、Queue等)对象的stream()方法Arrays的静态stream()方法,为此需要传入对应数组java import ... public class Main { public static void main(String[] args) { Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" }); Stream<String> stream2 = List.of("X", "Y", "Z").stream(); stream1.forEach(System.out::println); stream2.forEach(System.out::println); } }把现有的序列变为
Stream,它的元素固定 -
基于
SupplierStream的静态generate()方法,传入Supplier对象java Stream<String> s = Stream.generate(Supplier<String> sp);这种
Stream保存的不是元素,而是算法,因此,可表示无限序列(如果要迭代操作必须limit())```java import ... public class Main { public static void main(String[] args) { Stream
natual = Stream.generate(new NatualSupplier()); // 注意:无限序列必须先变成有限序列再打印: natual.limit(20).forEach(System.out::println); } } class NatualSupplier implements Supplier
{ int n = 0; public Integer get() { n++; return n; } } ``` 用时间来换空间,每次需要用到再算(惰性求值)(懒加载)
-
某些API提供接口
-
Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容,现代推荐java try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) { ... } -
正则表达式
Pattern对象的splitAsStream()方法,把一个长字符串分割成Stream序列而非数组java Pattern p = Pattern.compile("\\s+"); Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog"); s.forEach(System.out::println);
-
-
基本类型
Stream使用
Stream<Integer>时会频繁装箱、拆箱操作。为此,Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream使用方法和泛型Stream没有大的区别```java // 将int[]数组变为IntStream: IntStream is1 = Arrays.stream(new int[] { 1, 2, 3 }); // 将Stream
转换为LongStream: LongStream ls2 = List.of("1", "2", "3").stream().mapToLong(Long::parseLong); IntStream is3 = IntStream.range(1,100); IntStream is4 = IntStream.generate(() -> (int)(Math.random() * 100)) ```
-
迭代和生成
iterate()generate()区别 依赖前一个值 无状态,每次独立 参数 (seed, UnaryOperator)(Supplier)典型用途 数列、累加、递推 随机数、常量、独立值 是否可预测 可预测(公式驱动) 通常不可预测 能否无限 可无限 可无限 - 有状态迭代 iterate()基本语法 ```java // Java 8+ Stream.iterate(seed, unaryOperator) // Java 9+ 带谓词版本 Stream.iterate(seed, hasNext, next) ``` 常用 ```java // 偶数序列 IntStream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println); // 0,2,4,6,8,10,12,14,16,18 // 等比数列(翻倍) Stream.iterate(1, n -> n * 2) .limit(10) .forEach(System.out::println); // 1,2,4,8,16,32,64,128,256,512 ``` 带条件 ```java // 生成直到超过 100 Stream.iterate(1, n -> n < 100, n -> n * 2) .forEach(System.out::println); // 1,2,4,8,16,32,64 // 等价于 for 循环 for (int i = 1; i < 100; i *= 2) { System.out.println(i); } ```-
无状态生成
generate()java Stream.generate(supplier)常用
java // 每次独立调用 supplier,无状态传递 Stream.generate(Math::random) .limit(5) .forEach(System.out::println); // 0.123, 0.456, 0.789...(随机)
使用
iteratejava // 1. 序列有递推关系 Stream.iterate(1, n -> n * 2) // 等比数列 // 2. 需要累加/累积 Stream.iterate(0, acc -> acc + 5) // 步进累加 // 3. 数学数列 Stream.iterate(new int[]{0,1}, f -> new int[]{f[1], f[0]+f[1]}) // 斐波那契 // 4. 任何依赖前一个值的场景 Stream.iterate(LocalDate.now(), d -> d.plusDays(1)) // 日期序列使用
generatejava // 1. 随机数 Stream.generate(Math::random) // 2. 常量值 Stream.generate(() -> "constant") // 3. 独立计算(不依赖之前结果) Stream.generate(() -> new Object()) // 4. 从外部迭代器获取 Stream.generate(iterator::next) // 5. 测试数据生成 Stream.generate(() -> new User("test" + counter.getAndIncrement())) -
二、使用map
Stream.map()静态方法把一个Stream转换为另一个Stream
原理是把一个Stream的每个元素通过函数接口规则映射到另一个Stream
正如其源码,使用泛型把T类型映射成R
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
// Function定义是
@FunctionalInterface
public interface Function<T, R> {
// 将T类型转换为R:
R apply(T t);
}
利用map() ,极大地简化了代码
import ...
public class Main {
public static void main(String[] args) {
List.of(" Apple ", " pear ", " ORANGE", " BaNaNa ")
.stream() // 构造流
.map(String::trim) // 去空格
.map(String::toLowerCase) // 变小写
.forEach(System.out::println); // 打印
}
}
三、使用filter
Stream.filter()是Stream对象的另一个常用转换方法。
原理是对一个Stream的每个元素进行判断,留下满足条件的构成新Stream 对象
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(n -> n % 2 != 0) // 留下奇数
.forEach(System.out::println); // 1,3,5,7,9
}
}
filter()方法接收Predicate接口对象,其定义的test()方法,负责判断元素是否符合条件,常用匿名函数体内写布尔表达式实现
@FunctionalInterface
public interface Predicate<T> {
// 判断元素t是否符合条件:
boolean test(T t);
}
四、使用reduce
map()和filter()是Stream的转换方法,而Stream.reduce()则是Stream的一个聚合方法,把Stream对象的所有元素按照聚合函数聚合成一个结果
import java.util.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);
// 参数依次是
// 0:首次操作对象,初始值
// 第一个参数acc:上次操作的返回值
// 第二个参数n:该次的元素
System.out.println(sum); // 45
}
}
reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次操作的结果和本次的元素 进行二元操作,并返回操作的结果,以此迭代
原接口:
@FunctionalInterface
public interface BinaryOperator<T> {
// Bi操作:两个输入,一个输出
T apply(T t, T u);
}
当Stream的元素个数为0时,不能正常调用reduce()的聚合函数(二元函数),
为此,可以先不传初始值,获得一个Optional对象,进而判断结果是否存在
Optional<Integer> opt = stream.reduce((acc, n) -> acc + n);
if (opt.isPresent()) {
System.out.println(opt.get());
}
见下
Integer[][] nss={{},{1,2,3,4,5}};
for (Integer[] ns:nss) {
var option = Arrays.stream(ns).reduce(Integer::sum);
if (option.isPresent()) {
int sum = Arrays.stream(ns).reduce(0, Integer::sum);
System.out.println(sum);
}
}
五、转换与聚合
本质上是时间和空间上的选择,依据现有情况,决定时间优先还是空间优先
-
转换操作把一个
Stream转换为另一个Stream,例如map()和filter()转换操作不会触发任何计算,只能说保存了计算规则,懒加载
-
聚合操作对
Stream的每个元素进行计算,得到一个确定的结果,例如reduce()聚合操作会立刻促使
Stream输出它的每一个元素,并依次纳入计算
输出为List
把Stream的元素保存到集合,例如List,是一个聚合操作
使用collect() 方法,传入Collectors.toList()对象,可把Stream收集到List
类似的,collect(Collectors.toSet())可以把Stream的每个元素收集到Set
import ...
public class Main {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Apple", "", null, "Pear", " ", "Orange");
List<String> list = stream.filter(s -> s != null && !s.isBlank())
.collect(Collectors.toList());
System.out.println(list);
}
}
输出为数组
Stream的toArray()方法,并传入数组的“构造方法”,目的是指定类型
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
输出为Map
对于每个元素,添加到Map时需要key和value,为此,需要指定两个映射函数,分别把元素映射为key和value
import ...
public class Main {
public static void main(String[] args) {
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);
}
}
本文由 tazume-sans 原创,转载请注明出处。
评论
0