一、简介
1、stream 是Java API的新成员,他允许以声明性方式处理数据集合,还可以透明的并行处理
2、Stream API的好处
- 申明性——更简洁、易读
- 可复合——更灵活
- 可并行——性能更好
二、中间操作和终端操作
java.util.stream.Stream 中的Stream接口定义了许多操作,可以连接起来的流操作成为中间操作,关闭流的操作成为终端操作,分类只是基于流只能消费一次的事实
1、中间操作
中间操作会返回另一个流,多个操作可以连接起来可以形成一个查询,重要的是,除非流水线上触发一个终端操作,否则不会做任何处理。
常用中间操作:
操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
map | 中间 | Stream<R> | Function<T,R> | T -> R |
limit | 中间 | Stream<T> | ||
sorted | 中间 | Stream<T> | Comparator<T> | ( T, T) -> int |
distinct | 中间 | Stream<T> |
2、终端操作
终端操作会从流转化成结果,结果是任何不是流的值
常用终端操作:
操作 | 类型 | 目的 |
---|---|---|
forEach | 终端 | 消费流中的每个元素并对应其lambda,返回结果void |
count | 终端 | 返回流中元素的个数,返回结果long |
collect | 终端 | 把流规约成一个集合 |
三、使用流
- 流的使用一般包括三件事
一个数据源(如集合)来执行一个查询;
一个中间操作链,形成一条流的流水线;
一个终端操作,执行流水线,并能生成结果。
1、用谓词筛选(filter)
Streams接口支持filter方法,该操作会接受一个谓词作为参数,并返回一个包括所有符合谓词的元素的流。
List<Dish> list = menu.stram()
.filter(Dish :: isVegetarian)
.collect(toList());
2、去重(distinct)
流支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out :: println);
}
}
3、截短流(limit)
- 流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。
- limit也可以用在无序流上,比如源是一个Set。这种情况下, limit的结果不会以任何顺序排列
List<Dish> list = menu.stram()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
4、跳过元素(skip)
- 流支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。limit(n)和skip(n)是互补的!
List<Dish> list = menu.stram()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
5、映射(map)
- 流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(wordLengths.toString());
}
}
# 结果:[6, 7, 2, 6]
6、流的扁平化(flatMap)
- 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<String> uniqueCharacters = words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueCharacters.toString());
}
}
# 结果:[J, a, v, , 8, L, m, b, d, s, I, n, A, c, t, i, o]
7、查找和匹配
- 一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API通过allMatch、 anyMatch、 noneMatch、 findFirst和findAny方法提供了这样的工具,他们都是终端操作。
- anyMatch、 allMatch和noneMatch这三个操作都用到了我们所谓的短路,这就是大家熟悉的Java中&&和||运算符短路在流中的版本。
- 对于流而言,某些操作 (例如allMatch、anyMatch、noneMatch、findFirst和findAny)不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样, limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小的流的时候,这种操作就有用了:它们可以把无限流变成有限流。
7、 归约(reduce)
- reduce函数可以将流中所有元素反复结合起来,得到一个值。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold)。
- reduce提供两个版本,一个是带初始值,一个不带初始值(返回Optional对象)。
四、有状态和无状态
- 诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态。
- 诸如reduce、 sum、 max等操作需要内部状态来累积结果,从流中操作都需要知道先前的历史,因此是有状态操作
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中间(有状态-无界) | Stream<T> | ||
skip | 中间(有状态-有界) | Stream<T> | long | |
limit | 中间(有状态-有界) | Stream<T> | long | |
map | 中间 | Stream<R> | Function<T,R> | T -> R |
flatMap | 中间 | Stream<R> | Function<T,Stream<T>> | T -> Stream<R> |
sorted | 中间(有状态-无界) | Stream<T> | Comparator<T> | (T , T -> int) |
anyMatch | 终端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 终端 | boolean | Predicate<T> | T -> boolean |
allMatch | 终端 | boolean | Predicate<T> | T -> boolean |
findAny | 终端 | Optional<T> | ||
findFirst | 终端 | Optional<T> | ||
forEach | 终端 | void | Consumer<T> | T -> void |
collect | 终端 | R | Collector<T,A,R> | |
reduce | 终端(有状态-有界) | Optional<T> | BinaryOperator<T> | (T, T) -> T |
count | 终端 | long |
五、收集器
- 收集器非常有用,因为用它可以简洁而灵活地定义collect用来生成结果集合的标准。更具体地说,对流调用collect方法将对流中的元素触发一个归约操作。它遍历流中的每个元素,并让Collector进行处理。一般来说,Collector会对元素应用一个转换函数(例如toList),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。
● 预定义收集器
工厂方法 | 返回类型 | 用于 | 使用示例 |
---|---|---|---|
toList | List<T> | 把流中所有项目收集到一个List | List<Dish> dishs = menuStream.collect(toList()); |
toSet | Set<T> | 把流中所有项目收集到一个Set,删除重复项 | Set<Dish> dishs = menuStream.collect(toSet()); |
toSet | Set<T> | 把流中所有项目收集到一个Set,删除重复项 | Set<Dish> dishs = menuStream.collect(toSet()); |
toCollection | Collection<T> | 把流中所有项目收集到给定的供应源创建的集合 | Collection<Dish> dishs = menuStream.collect(toCollection,ArrayList :: new); |
counting | Long | 计算流中元素的个数 | long howManyDishes = menuStream.collect(counting()) |
summingInt | Integer | 对流中的项目一个整数属性求和 | int totalCalories = menuStream.collect(summingInt(Dish :: getCalories)); |
averagingInt | Double | 计算流中项目Integer属性的平均值 | double avgCalories = menuStream.collect(averagingInt(Dish :: getCalories)); |
summarizingInt | IntSummaryStatistics | 收集关于流中项目Integer属性的统计值,例如最大、最小、总和、平均值 | IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish :: getCalories)); |
joining | String | 连接对流中每个项目toString方法所生成的字符串 | String shortMenue = menuStream.map(Dish :: getName).collect(joining(",")); |
maxBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最大元素的Optional,或如果流为空则为Optional.empty() | Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish :: getCalories))); |
minBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最小元素的Optional,或如果流为空则为Optional.empty() | Optional<Dish> lighttest = menuStream.collect(minBy(comparingInt(Dish :: getCalories))); |
reducing | 归类操作产生的类型 | 从一个作为类加载器的初始值开始,利用BinaryOperator 与流中的元素逐个集合,从而将流规约为单个值 | int totalCalories = new menuStream.collect(reducing(0,Dish :: getCalories,Integer :: sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果应用转换函数 | int howManyDishes = new menuStream.collect(collectingAndThen (toList(),List :: size); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果应用转换函数 | int howManyDishes = new menuStream.collect(collectingAndThen (toList(),List :: size); |
groupingBy | Map<K,List<T>> | 根据项目的一个属性的值对流中的项目做问组,并将属性值作为结果Map的键 | Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish :: getType)); |
partitioningBy | Map<Boolean,List<T>> | 根据对流中每个项目应用谓词的结果来对项目进行分区 | Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish :: isVegetarian)); |
本文作者:魂皓轩 欢迎关注公众号
本人保留所有权益,转载请注明出处。
欢迎有故事、有想法的朋友和我分享,可发送至 e-mail: lwqforit@163.com
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。