余子越的博客
Toggle navigation
余子越的博客
主页
计算机网络
大数据分析
系统与工具
编程之路
容器引擎
作者
归档
标签
Java之惰性计算Stream
2020-01-30 22:37:44
169
0
0
yuziyue
[TOC] > Java从8开始,不但引入了Lambda表达式,还引入了一个全新的流式API:Stream API。它位于java.util.stream包中。这个Stream不同于java.io的InputStream和OutputStream,它代表的是任意Java对象的序列,它类似Python中的迭代器。 # 一. 创建Stream ## 1.1 Stream.of() 创建`Stream`最简单的方式是直接用`Stream.of()`静态方法,传入可变参数即创建了一个能输出确定元素的`Stream`,虽然这种方式基本上没啥实质性用途,但测试的时候很方便。 ``` import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> stream = Stream.of("A", "B", "C", "D"); stream.forEach(System.out::println); } } ``` <br> ## 1.2 基于数组或Collection - 把数组变成`Stream`使用`Arrays.strem()`方法。 - 对于`Collection`(`List、Set、Queue`等),直接调用`stream()`方法就可以获得`Stream`。 ``` import java.util.ArrayList; import java.util.List; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" }); stream1.forEach(System.out::println); List<String> stringOne = new ArrayList<>(); stringOne.add("a"); stringOne.add("b"); stringOne.add("c"); Stream<String> stream2 = stringOne.stream(); stream2.forEach(System.out::println); Set<String> setOne = new HashSet<>(); setOne.add("1"); setOne.add("2"); setOne.add("3"); Stream<String> stream3 = setOne.stream(); stream3.forEach(System.out::println); } } ``` <br> ## 1.3 基于`Supplier` 创建`Stream`还可以通过`Stream.generate()`方法,它需要传入一个`Supplier`对象,基于`Supplier`创建的`Stream`会不断调用`Supplier.get()`方法来不断产生下一个元素,这种`Stream`保存的不是元素,而是算法,它可以用来表示无限序列。例如,我们编写一个能不断生成自然数的`Supplier`,它的代码非常简单,每次调用`get()`方法,就生成下一个自然数: ``` import java.util.function.Supplier; import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<Integer> natual = Stream.generate(new NatualSupplier()); natual.limit(20).forEach(System.out::println); } } class NatualSupplier implements Supplier<Integer> { int n = 0; public Integer get() { n++; return n; } } ``` <br> # 二. 基本类型 因为`Java`的范型不支持基本类型,所以我们无法用`Stream<int>`这样的类型,会发生编译错误。为了保存`int`,只能使用`String<Integer>`,但这样会产生频繁的装箱、拆箱操作。为了提高效率,`Java`标准库提供了`IntStream`、`LongStream`和`DoubleStream`这三种使用基本类型的Stream,它们的使用方法和范型`Stream`没有大的区别,设计这三个`Stream`的目的是提高运行效率: ``` IntStream is = Arrays.stream(new int[] { 1, 2, 3 }); // 将int[]数组变为IntStream LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong); // 将Stream<String>转换为LongStream ``` <br> # 三. 常用Stream函数 ## 3.1 Stream.map() `Stream.map()`是`Stream`最常用的一个转换方法,它把一个`Stream`转换为另一个`Stream`。所谓`map`操作,就是把一种操作运算,映射到一个序列的每一个元素上。如果我们查看`Stream`的源码,会发现`map()`方法接收的对象是`Function`接口对象,它定义了一个`apply()`方法,负责把一个`T`类型转换成`R`类型 ``` import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<Integer> s = Stream.of(1, 2, 3, 4, 5); Stream<Integer> s2 = s.map(n -> n * n); s2.forEach(System.out::println); } } ``` ``` import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> s = Stream.of(" Apple ", " pear ", " ORANGE", " BaNaNa "); s.map(String::trim) .map(String::toLowerCase) .map(String::length) .forEach(System.out::println); } } ``` <br> ## 3.2 Stream.filter() 所谓`filter()`操作,就是对一个`Stream`的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的`Stream`。`filter()`方法接收的对象是`Predicate`接口对象,它定义了一个`test()`方法,负责判断元素是否符合条件。 ``` import java.util.stream.IntStream; public class Main { public static void main(String[] args) { IntStream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) .filter(n -> n % 2 == 0) .forEach(System.out::println); } } ``` **注意**:`filter`的意思是筛选出满足条件的项(返回`true`的项)。 <br> ## 3.3 Stream.reduce() `Stream.reduce()`则是`Stream`的一个聚合方法,它可以把一个`Stream`的所有元素按照聚合函数聚合成一个结果。`reduce()`方法传入的对象是`BinaryOperator`接口,它定义了一个`apply()`方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果。`reduce()`操作需要初始化结果为指定值(这里是0)。 ``` import java.util.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, (x, y) -> x + y); // 求 1 到 9 的和 System.out.println(sum); } } ``` **注意**:`reduce()`是聚合方法,聚合方法会立刻对`Stream`进行计算。 <br> # 四. 聚合操作 `reduce()`只是一种聚合操作,如果我们希望把`Stream`的元素保存到集合,例如`List`,因为`List`的元素是确定的`Java`对象,因此,把`Stream`变为`List`不是一个转换操作,而是一个聚合操作,它会强制`Stream`输出每个元素。 <br> ## 4.1 输出为List 把`Stream`的每个元素收集到`List`的方法是调用`collect()`并传入`Collectors.toList()`对象,它实际上是一个`Collector`实例,通过类似`reduce()`的操作,把每个元素添加到一个收集器中(实际上是`ArrayList`)。 ``` import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> stream = Stream.of("Apple", null, "Pear", null, "Orange"); List<String> list = stream.filter(s -> s != null).collect(Collectors.toList()); System.out.println(list); } } ``` <br> ## 4.2 输出为Set ``` Stream<String> stream = Stream.of("Apple", null, "Pear", null, "Orange"); Set<String> set = stream.filter(s -> s != null).collect(Collectors.toSet()); ``` <br> ## 4.3 输出为数组 把`Stream`的元素输出为数组和输出为`List`类似,我们只需要调用`toArray()`方法,并传入数组的“构造方法”。注意到传入的“构造方法”是`String[]::new`,它的签名实际上是`IntFunction<String[]>`定义的`String[] apply(int)`,即传入`int`参数,获得`String[]`数组的返回值。 ``` List<String> list = List.of("Apple", "Banana", "Orange"); String[] array = list.stream().toArray(String[]::new); ``` <br> ## 4.4 输出为Map 如果我们要把`Stream`的元素收集到`Map`中,就稍微麻烦一点。因为对于每个元素,添加到`Map`时需要`key`和`value`,因此,我们要指定两个映射函数,分别把元素映射为`key`和`value`: ``` import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; 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 -> s.substring(0, s.indexOf(':')), s -> s.substring(s.indexOf(':') + 1))); System.out.println(map); } } ``` <br> ## 4.5 输出为分组 `Stream`还有一个强大的分组功能,可以按组输出。分组输出使用`Collectors.groupingBy()`,它需要提供两个函数:一个是分组的`key`,这里使用`s -> s.substring(0, 1)`,表示只要首字母相同的`String`分到一组,第二个是分组的`value`,这里直接使用`Collectors.toList()`,表示输出为`List` ``` import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<String> list = Arrays.asList("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); } } ``` <br> # 五. 其他聚合方法 - 除了`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<String> s = ... s.forEach(str -> { System.out.println("Hello, " + str); }); ``` <br> # 六. 其他操作 ## 6.1 排序 此方法要求`Stream`的每个元素必须实现`Comparable`接口。如果要自定义排序,传入指定的`Comparator`即可。注意`sorted()`只是一个转换操作,它会返回一个新的`Stream`。 ``` List<String> list = Stream.of("Orange", "apple", "Banana") .sorted(String::compareToIgnoreCase) .collect(Collectors.toList()); System.out.println(list); ``` <br> ## 6.2 去重 ``` Stream.of("A", "B", "A", "C", "B", "D") .distinct() .forEach(System.out::println); ``` <br> ## 6.3 截取 截取操作常用于把一个无限的`Stream`转换成有限的`Stream`,`skip()`用于跳过当前`Stream`的前`N`个元素,`limit()`用于截取当前`Stream`最多前`N`个元素,截取操作也是一个转换操作,将返回新的`Stream`。 ``` Stream.of("A", "B", "C", "D", "E", "F") .skip(2) // 跳过A, B .limit(3) // 截取C, D, E .collect(Collectors.toList()); // [C, D, E] ``` <br> ## 6.4 合并 将两个`Stream`合并为一个`Stream`可以使用`Stream`的静态方法`concat()` ``` Stream<String> s1 = Stream.of("A", "B", "C"); Stream<String> s2 = Stream.of("D", "E"); Stream<String> s = Stream.concat(s1, s2); s.forEach(System.out::println); // [A, B, C, D, E] ``` <br> ## 6.5 flatMap 如果`Stream`的元素是集合,而我们希望把上述`Stream`转换为`Stream<Integer>`,就可以使用`flatMap()` ``` Stream<List<Integer>> s = Stream.of( Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9)); Stream<Integer> i = s.flatMap(list -> list.stream()); ``` 因此,所谓`flatMap()`,是指把`Stream`的每个元素(这里是`List`)映射为`Stream`,然后合并成一个新的`Stream` ``` ┌─────────────┬─────────────┬─────────────┐ │┌───┬───┬───┐│┌───┬───┬───┐│┌───┬───┬───┐│ ││ 1 │ 2 │ 3 │││ 4 │ 5 │ 6 │││ 7 │ 8 │ 9 ││ │└───┴───┴───┘│└───┴───┴───┘│└───┴───┴───┘│ └─────────────┴─────────────┴─────────────┘ │ │flatMap(List -> Stream) │ ▼ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ └───┴───┴───┴───┴───┴───┴───┴───┴───┘ ``` <br> ## 6.6 并行 通常情况下,对`Stream`的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理`Stream`的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。把一个普通`Stream`转换为可以并行处理的`Stream`非常简单,只需要用`parallel()`进行转换。经过`parallel()`转换后的`Stream`只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。 ``` Stream<String> s = ... String[] result = s.parallel() // 变成一个可以并行处理的Stream .sorted() // 可以进行并行排序 .toArray(String[]::new); ``` <br><br><br>
上一篇:
Chrome 插件vimium快捷键
下一篇:
Java反射
0
赞
169 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
文档导航