此文数值流、来自文件和数组等多种来源的流,最后是无限流
一.数值流
我们在可以使用reduce方法计算流中元素的总和。例如,你可以像下面这样计算菜单的热量:
int calories = menu.stream().map(Dish::getCalories) .reduce(0, Integer::sum);
这段代码的问题是,它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型,再进行求和。
Stream API还提供了原始类型流特化
,专门支持处理数值流的方法。
map方法会生成一个Stream。虽然流中的元素是Integer类型,但Streams接口没有定义sum方法。
- 原始类型流特化
Java 8引入了三个原始类型特化流接口:
IntStream
、DoubleStream
和LongStream
,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。
每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。
这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性,即类似int和Integer之间的效率差异。
- [1]映射到数值流
将流转换为特化版本的常用方法是
mapToInt
、mapToDouble
和mapToLong
。这些方法和map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream<T>。
例如,用mapToInt对menu中的卡路里求和:
int calories = menu.stream()//返回一个Stream<Dish>
.mapToInt(Dish::getCalories)//返回一个IntStream
.sum();
这里,mapToInt会从对象中获取calories属性,用一个Integer表示,并返回一个IntStream(而不是一个Stream)。然后调用IntStream接口中定义的sum方法求和了。
如果流是空的,sum默认返回0。
IntStream还支持其他的方便方法,如max、min、average等。
- [2]转换回对象流
IntStream上的操作只能产生原始整数: IntStream 的 map 操作接受的 Lambda 必须接受 int 并返回 int (一个IntUnaryOperator)。
要把原始流转换成一般流可以使用boxed
方法。
例如:
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
- [3]默认值OptionalInt
求和因为有一个默认值:0。如果你要计算IntStream中的最大元素0是错误的结果。
Optional可以用Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt
、OptionalDouble
和OptionalLong
。
例如:
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
//如果没有最大值的话,可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);
- [4]数值范围
Java 8引入了两个可以用于IntStream和LongStream的静态方法(
range
和rangeClosed
),帮助生成数值范围,这两个方法都是第一个参数接受起始值,第二个参数接受结束值。range是不包含结束值的,而rangeClosed则包含结束值。
例如,生成1到100之间的所有数字,偶数出现个数:
IntStream evenNumbers = IntStream.rangeClosed(1, 100)//表示范围[1, 100]
.filter(n -> n % 2 == 0); //筛选偶数
System.out.println(evenNumbers.count());
结果:
50
如果改用IntStream.range(1, 100),则结果将会是49个偶数,因为range是不包含结束值的。
二.构建流
- 由值创建流
使用静态方法Stream.of,通过显式值创建一个流。
可以接受任意数量的参数。
例如,以下代码直接使用Stream.of
创建了一个字符串流。然后,你可以将字符串转换为大写:
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
也可以使用empty得到一个空流:
Stream<String> emptyStream = Stream.empty();
- 由数组创建流
使用静态方法
Arrays.stream
从数组创建一个流。
它接受一个数组作为参数。
例如,将一个原始类型int的数组转换成一个IntStream:
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
- 由文件生成流
java.nio.file.Files
中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines
,它会返回一个由指定文件中的各行构成的字符串流。
long uniqueWords = 0;
try (Stream<String> lines =
Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
}
- 由函数生成流:创建无限流
Stream API提供了两个静态方法来从函数生成流:
Stream.iterate
和Stream.generate
。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。
由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去。一般来说,
应该使用limit(n)
来对这种流加以限制,以避免打印无穷多个值。
- [ 1] 迭代
iterate的简单例子:
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
iterate方法生成了一个所有正偶数的流:流的第一个元素是初始值0。然后加上2来生成新的值2,再加上2来得到新的值4,以此类推。
这种iterate操作基本上是顺序的,因为结果取决于前一次应用。此操作将生成一个无限流,这个流是无界、没有结尾,因为值是按需计算的,可以永远计算下去。
- [ 2] 生成
与iterate方法类似,generate方法也可让你需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。
Stream.generate(Math::random).limit(5).forEach(System.out::println);
这段代码将生成一个流,其中有五个0到1之间的随机双精度数。
Math.Random静态方法被用作新值生成器。用limit方法显式限制流的大小,否则流将会无限长。
结果:
0.9410810294106129
0.6586270755634592
0.9592859117266873
0.13743396659487006
0.3942776037651241