流
流简介
- 流是从支持数据处理操作的源生成的元素序列。
- 集合的核心是数据,而流的目的在于表达计算。
- 和迭代器类似,流只能遍历一次。
- 使用
Collection
接口需要用户做迭代,称为外部迭代。而使用Stream
则代替用户做了迭代,称为内部迭代。
流操作
List<String> names = menu.stream()
.filter(d -> d.getCalories())
.map(Dist::getName)
.limit(3)
.collect(toList());
- 诸如
filter
或sorted
等中间操作会返回另一个流,这让多个操作可以连接起来形成一个查询。除非流水线上触发一个终端操作,否则中间操作不会执行任何处理,因为中间操作一般都可以合并起来,在终端操作时一次性全部处理,这种技术称作循环合并。
- 终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如
List
、Integer
,甚至是void
。
使用流
筛选和切片
用谓词筛选
filter
方法:接受一个谓词(返回boolean
的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
提取不同元素
distinct
方法:无参数,返回一个元素各异的流(根据流所生成元素的hashCode()
和equals()
方法实现)。
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
截短流
limit(n)
方法:返回一个不超过给定长度n
的流。如果流是有序的,则最多返回钱n
个元素;如果流是无序的(例如Set
),则返回结果不会以任何顺序排列。
跳过元素
skip(n)
方法:返回一个去掉前n
个元素的流。如果流中的元素不足n
个,则返回一个空流。与limit(n)
是互补操作。
映射
对流中的每个元素应用函数
map
方法:接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素(与转换的区别是,该步骤是创建一个新的版本而不是修改)。
List<String> dishName = menu.stream()
.map(Dish::getName)
.collect(toList());
流的扁平化
flatMap
方法:传入的方法生成的流不会各自独立,而是会被合并,扁平化称为一个流。
查找和匹配
anyMatch
检查谓词是否至少匹配一个元素;allMatch
会检查谓词是否匹配所有元素,相对的noneMatch
则会确保流中没有任何元素与给定的谓词匹配。这三种方法都用到了短路。
findAny
可以返回流中的任意元素,而findFirst
则返回有出现顺序的流中的第一个元素。使用时配合Optional<T>
使用。此外findAny
在使用并行流时限制较少,如果不关心返回哪个元素,那么应该使用findAny
。
归约
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
- 上面代码展示了用
reduce
求一组整数的和,reduce
接受一个初始值和一个函数,它将使用函数反复结合每个元素,直到流被归约成一个值。
- 使用
reduce
可以让内部实现得以选择并行执行操作。但是,传递给reduce
的Lambda不能更改状态(如实例变量),而且操作必须满足结合律才可以按任意顺序执行。
数值流
IntStream
、DoubleStream
和LongStream
分别将流中的元素特化为int
、double
和long
,从而避免的暗含的装箱成本。数值流包含进行常用归约的方法,如求和sum
,找到最大元素max
等。
- 映射到数值流可以用
mapToInt
、mapToDouble
和mapToLong
,而使用boxed
方法可以将数值流转回对象流(各个基本类型对应的包装类型)。
range
和rangeClosed
是可以用于IntStream
和LongStream
的静态方法,作用是生成范围内的数,区别是range
不包含结束值而rangeClosed
包含。
构建流
由值创建流
- 使用
Stream.of
可以通过显式值创建一个流,可以接受任意数量的参数。而Stream.empty
可以得到一个空流。
Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
Stream<String> emptyStream = Stream.empty();
由数组创建流
- 静态方法
Arrays.stream
可以从数组创建一个流,接受一个数组作为参数。
由文件生成流
java.nio.file.Files
中的很多静态方法都会返回一个流,例如Files.lines
,它会返回一个由指定文件中的各行构成的字符串流。
由函数生成流——创建无限流
迭代
iterate
方法接受一个初始值,以及一个一次应用在每个产生的新值上的Lambda。iterate
操作基本上是顺序的,因为结果取决于前一次应用。该操作将生成一个无限流,没有结尾,因为值是按需计算的,可以永远计算下去。因此流和集合的一个关键的区别在于流是无界的。
生成
generate
方法也可以生成无限流,它接受一个Supplier<T>
类型的Lambda提供新的值。