StreamApi使用
介绍
stream是Java8推出的一大亮点,它是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。
什么是流
Stream不是集合或者是其他数据结构,他也不保存数据,它做的只是一系列的计算。它犹如一个迭代器Iterator,单向执行,遍历完一次后就结束,就像流水一样一去不复返。
流的使用步骤
获取流(数据源)->操作流(数据转换)->结束流(执行想要获取的结果)
在每一次操作流之后都返回了一个新的Stream对象,这样就实现了链式调用,十分方便
获取流
Collection 和数组:
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of()
BufferReader:
- java.io.BufferedReader.lines()
静态工厂:
java.util.stream.IntStream.range()
Stream提供了几种专门操作基本类型的流:
IntStream LongStream DoubleStream
如果要操作的数据常规性的数值操作,可以直接使用这几种Stream
其他:
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
示例:
String [] strArray = new String[] {"a", "b", "c"};
数组:Arrays.stream(strArray) Stream.of(strArray)
List<String> list = Arrays.asList(strArray);
Collection :list.stream();
Stream.of("a", "b", "c");
流的操作Api
当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了
对Api进行分类
Intermediate:
一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,
做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、
parallel、 sequential、 unordered
Terminal:
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
Short-circuiting:
- 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的
Stream,但返回一个有限的新 Stream。 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
map
将输入的流的每一个元素一对一的按某种规则转换成另一个元素输出
例:将输入的字符转换成小写
Stream.of("A","B","C").map(String::toLowerCase).collect(Collectors.
toList());
flatmap
针对的情况是一对多,即Stream中的每一个元素是可以进行再一次Stream操作
示例:将集合中的子集合全部拆分为单个元素
Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
).flatMap((childList) -> childList.stream()).collect(Collectors.toList());
结果:[1,2,3,4,5,6]
filter
按某种规则过滤掉数据源中不需要的数据
示例:过滤掉小于5的数据
IntStream.of(1, 2, 3, 4, 5).filter(value -> value > 4);
distinct
去除数据源中的重复数据
List<String>map=Stream.of("A","B","B","C").distinct().collect(Collectors.toList());
输出:[A, B, C]
sorted
对数据源中的数据进行排序
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
根据自己定义的Comparator实现排序规则
peek/forEach
两者作用一致,只不过peek是属于Intermediate操作,forEach属于Terminal操作,即对于一个Stream而言调用了forEach后不能再继续执行操作,因为事件已经被消费了。
非法操作:
- stream.forEach(element -> doOneThing(element));
- stream.forEach(element -> doAnotherThing(element));
limit/skip
两者作用相反,limit作用是取流中的前几个元素,skip作用是跳过流中的前几个元素。
List<String> map = Stream.of("A", "B","B", "C").limit(3).collect(Collectors.toList());
输出:[A, B, B]
List<String> skip = Stream.of("A", "B","B", "C").skip(3).collect(Collectors.toList());
输出:[C]
Match
查看是否匹配,分为三种:
- anyMatch 任意一个元素匹配即为真
- allMatch 匹配所有元素才为真
- noneMatch 没有一个匹配的元素为真
min/max
找到流中的最大最小值,也可以通过对流进行sort然后findFirst()获取
reduce
将流中的每一个元素都组合起来,产生一个全新的值
它需要我们首先提供一个起始种子,然后依照某种运算规则使其与stream的第一个元素发生关系产生一个新的种子,这个新的种子再紧接着与stream的第二个元素发生关系产生又一个新的种子,就这样依次递归执行,最后产生的结果就是reduce的最终结果。
从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce
// 字符串连接,concat = “ABCD”
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE,
Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = “ace”
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
结束流(获取想要的执行结果)
用 Collectors 来进行 reduction 操作
java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
按书的作者分组
List<Book> books = new ArrayList<>();
books.add(new Book("大主宰", "天蚕土豆", 50.0f));
books.add(new Book("盘龙", "我吃西红柿", 40.0f));
books.add(new Book("斗罗大陆", "唐家三少", 50.0f));
books.add(new Book("斗破苍穹", "天蚕土豆", 80.0f));
Map<String, List<Book>> collect = books.stream().collect(Collectors.groupingBy
(new Function<Book, String>() {
@Override
public String apply(Book book) {
return book.getAuthor();
}
}));
总结
Stream的特性:
- 不是数据结构
- 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
- 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
- 所有 Stream 的操作必须以 lambda 表达式为参数
- 不支持索引访问
- 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。
- 很容易生成数组或者 List
- 惰性化
- 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
- Intermediate 操作永远是惰性化的。
- 并行能力
- 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
- 可以是无限的
- 集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。