在本篇中,你将会看到Stream API支持的许多操作。这些操作能让你快速完成复杂的数据查询,如筛选、切片、映射、查找、匹配、和归约。接下来,我们会看看一些特殊的流:数值流、来自文件和数组等多种来源的流,最后是无限流。
- 筛选和切片
- 用谓词筛选(筛选):Stream接口支持filter方法。该操作会接受一个谓词(一个返回Boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
- 筛选各异的元素(去重):流还支持一个叫做distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法的实现)的流。
- 截短流(截取):流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。
- 跳过元素(跳过):流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,limit(n)和skip(n)是互补的!
- 映射
- 对流中每一个元素应用函数:流支持map方法,它会接受一个函数(Function)作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是创建一个“新版本”,而不是去“修改”)。
- 流的扁平化:使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Array::Stream)时生成的单个流都被合并起来,即扁平化为一个流。
- 查找和匹配
- 检查谓词是否至少匹配一个元素:anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。
- 检查谓词是否匹配所有元素:allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。
- 查找元素:findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。
- 查找第一个元素:有些流有一个出现顺序来指定流中项目出现的逻辑顺序(比如由List或排序好的数据列生成的流)。对于这种流,你可能想要找到第一个元素。为此有一个findFirst方法,它的工作方式类似于findAny。
- 归约
- 元素求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
- 一个初始值,这里是0;
- 一个BinaryOperator((T,T) -> T)来将两个元素结合起来产生一个新值,这里我们用的是lambda (a,b) -> a+b。 reduce接受两个参数:
你可以使用方法引用让这段代码更简洁。在Java8里,Integer类现在有一个静态的sum方法来对两个数求和,这恰好是我们想要的,用不着反复用Lambda写同一段代码了:
int sum = numbers.stream().reduce(0, Integer::sum);
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
- 最大值和最小值
原来,只要用归约就可以计算最大值和最小值了!让我们来看看如何利用刚刚学到的reduce来计算流中最大或最小的元素。正如你前面看到的,reduce接受两个参数:
- 一个初始值;
- 一个Lambda来把两个流元素结合起来并产生一个新值
Optional<Integer> max = number.stream().reduce(Integer::max);
Optional<Integer> max = number.stream().reduce(Integer::min);
复制代码
当然你也可以写成Lambda (x, y) -> x < y ? x : y
- 数值流
- 原始类型流特化
Java8引入了三个原始类型特化流接口:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性--即类似int和Integer之间的效率差异。
将流映射为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream<T>。例如:
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
复制代码
这里,mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream(而不是一个Stream<Integer>)。然后你就可以调用IntStream接口中定义的sum方法,对卡路里求和了!请注意,如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如max、min、average等。
同样,一旦有了数值流,你可能回想把它转换回非特化流。例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的Lambda必须接受int并返回int(一个IntUnaryOperator)。但是你可能想生成另一类值,比如Dish。为此,你需要访问Stream接口中定义的那些更广义的操作。要把原始流转换为一般流(每个int都会装箱成一个Integer),可以使用boxed方法:
IntStream intStream = menu.stream().map(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
复制代码
对于三种原始特化流,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getColories)
.max();
复制代码
现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);//如果没有最大值,显式提供一个默认对大值
- 数值范围
Java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但是range是不包含结束值的,而rangeClosed包含结束值。
- 构建流
- 由值构建流
你可以使用静态方法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, 4, 5, 6}; int sum = Arrays.stream(numbers).sum();
-
由文件生成
-
由函数生成流:创建无限流
- 迭代(iterator)
我们先来看一个iterator的简单例子,然后在解释:
Stream.iterator(0, n -> n + 2) .limit(10) .forEach(System.out::println); 复制代码
iterator方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。因此,iterator方法生成了一个所有正偶数的流:流的第一个元素是初始值0。请注意,此操作将生成一个无限流--这个流没有结尾,因为值是按需计算的,可以永远计算下去。我们使用limit方法来显式限制流的大小。这里只选择了前10个偶数,然后可以调用forEach终端操作来消费流,并分别打印每个元素。
- 生成(generate)
与iterator方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier<T>类型的Lambda提供新的值。我们先来看一个简单的用法:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
这段代码将生成一个流,其中有五个0到1之间的随机双精度数。