首先推荐这篇,这是我看过的所有关于 stream 的文章中讲的最好的一篇。
第一次接触 stream 是操作集合时,我们组长推荐我看的,看完后觉得这简直就是神器,而且 java 代码中会大量使用集合,stream 极高的帮我们提高了编码效率。当然,它远比我们想的更强大。
除了操作集合,他还可以操作数组,当然还有 Stream 本身。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。
在学习 stream 的常用 api 之前,还有几点需要了解的:
1.Intermediate 与 Terminal
举个例子:
list = list.stream().filter(item -> item>100).collect(toList());
这个聚合操作中的filter(item -> item>100)
便被称为Intermediate,collect(toList())
便被称为Terminal。这两者的顺序就和英文原意一样,Intermediate 是中间的,Terminal 是终端的,一个 stream 流可以有多个 Intermediate,但只能有一个 Terminal,并且多个 Intermediate 并不是进行多次集合的迭代,而是将多个 Intermediate 融合在一起,并且在最后一步 Terminal 操作进行时进行迭代。
所以,如果有这样一个操作:
image-20181118152908271我们创建了一个流,表面上看我们好像有一步过滤操作,在输出那边打了一个断点,然后会发现并不会执行,这时候我们在尾端加一个 Terminal 操作才会让过滤操作执行。
2.构造流的三种常用方式
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
实际工作中我们最常用的是集合,所以第三种是最常用的。
3.流转换为其他数据格式
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();
4. 返回值
每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换)
流的常用操作
-
Intermediate:
- map (mapToInt, flatMap 等):映射
- filter:过滤
- distinct:去重
- sorted: 排序
- peek:直译窥视,有点猥琐,可以理解为监视
- limit: 限制为前几位元素
- skip:跳过前几位元素
- parallel:并行
- sequential:顺序
- unordered:无序
-
Terminal:
- forEach:遍历
- findFirst:找出第一个,并返回,如果为空集合返回 null
- reduce:汇总
- anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
- allMatch:Stream 中全部元素符合传入的 predicate,返回 true
- noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
- collect:转换为集合
- min:最小值
- max:最大值
- count:返回元素的数量
- findAny:找出任何一个就返回
- iteratorto:转换为 Iterator 类型的
- Array:转换为数组
-
Short-circuiting:
- anyMatch
- allMatch
- noneMatch
- findFirst
- findAny
- limit
Short-circuiting 刚才没说,Short-circuiting 主要是操作无限大的 stream,上面列举的既是 Terminal 也是 Short-circuiting,用法同上。
Intermediate
1. map
map 中文翻译可以理解为映射,就是你进来多少,出去还是多少,元素会有变化,但不会减少,也不会增多。看代码:
List<String> charList = Arrays.asList("a","b","c");
charList = charList.stream().map(item -> item = item.toUpperCase()).collect(toList());
上面一段代码就是把 abc 转换成了 ABC,进来三个,回来还是三个
文首链接中的举例中使用了双冒号的语法糖,把 item -> item = item.toUpperCase() 替换成了 String::toUpperCase,更加简洁了。
1.1 flatMap
其实 map 的兄弟flatMap可以一对多,第一次看到感觉有点像 ES6中的解构赋值。
List<String> charList1 = Arrays.asList("a","b","c");
List<String> charList2 = Arrays.asList("e","f");
List<String> charList3 = Arrays.asList("q");
Stream<List<String>> stream = Stream.of(charList1,charList2,charList3);
Stream<String> streamTotal = stream.flatMap((childList) -> childList.stream());
List<String> stringList = streamTotal.collect(toList());
2.filter
filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
// 返回大于100的值,小于等于100的都被过滤了
List<Integer> list = Arrays.asList(13,34,56,23,776,322,545);
list = list.stream().filter(item -> item>100).collect(toList());
3.distinct
去重,把重复的元素只保留一个
List<Integer> list = Arrays.asList(13,34,34,23,34,322);
list = list.stream().distinct().collect(toList()); // 13,34,23,322
4.sorted
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间
List<Integer> list = Arrays.asList(13,34,34,23,34,322,3,54,76,45);
list = list.stream().distinct().sorted().collect(toList());
除了数字大小排序,我们基本都需要自己定义排序规则
Student student1 = new Student.Builder(10,"aaa").build();
Student student2 = new Student.Builder(7,"afa").build();
Student student3 = new Student.Builder(40,"gaa").build();
Student student4 = new Student.Builder(4,"aai").build();
Student student5 = new Student.Builder(9,"qaa").build();
List<Student> studentList = Arrays.asList(student1,student2,student3,student4,student5);
// 按学生 id 倒序排列
studentList = studentList.stream().sorted(Comparator.comparing(Student::getId).reversed()).collect(toList());
// 按学生 name 字母排列
studentList = studentList.stream().sorted((c1,c2) -> c1.getName().compareTo(c2.getName())).collect(toList());
5.peek
如果我们在中间环节想看下集合元素(不操作数组,只是取值),我们好像可以使用 map 来循环一下,但很明显 map 本意是来操作值的,而且 IDEA 也提示我们用 peek 去代替,我理解 peek 的作用就是如果我们不操作转换成 stream 的集合本身的的元素,只是取他的值或者用这些值给别的变量赋值,那我们就应该用 peek。
image-20181118183128171所以上面的代码我们可以修改成这样:
List<Integer> list = Arrays.asList(13,34,34,23,34,322,3,54,76,45);
list = list.stream().map(item -> item+1).distinct().peek(System.out::println).sorted().collect(toList());
上面我们说过只有最后Terminal操作时才执行,我就想到会不会输出的是最后的排序后的元素,试了下,发现并不是,方法内部都帮我们处理好了,我们就不用操心这些问题了。
6.limit
返回前几位元素
List<Integer> list = Arrays.asList(13,34,34,23,34,322,3,54,76,45);
list = list.stream().limit(3).peek(System.out::println).collect(toList());
// 13 34 34
7.skip
跳过前几位,返回后面的元素
List<Integer> list = Arrays.asList(13,34,34,23,34,322,3,54,76,45);
list = list.stream().skip(3).peek(System.out::println).collect(toList());
// 23 34 322 3 54 76 45
8.parallel
网上资料不多,就直接看源码上的注释吧
Returns an equivalent stream that is parallel. May return itself, either because the stream was already parallel, or becaus the underlying stream state was modified to be parallel.
大致意思就是让流并行,如果已经是个 parallelStream,则返回本身,如果是 普通 Stream,则使其并行。
也就是说:list.stream().parallel()
= list.parallelStream()
9.sequential / unorder
涉及到一个遇到顺序的问题,比较复杂,可以参照这篇
Terminal
1.forEach()
forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。这么看起来和 map 好像有点像,都是迭代,但其实不一样,首先 map 是 Intermediate 的操作,而 foreach 是 Terminal 操作,其次 map 的返回值是原来的数据类型,而 foreach 的返回值是空,也就是说 foreach 并不会影响原集合。如下,好像我们给每个元素加1了,但输出的结果还是原集合,并没有改变。
List<Integer> list = Arrays.asList(1,2,3);
list.stream().forEach(item -> item = item+1); // [1,2,3]
另外一点需要注意,forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算。
2.findFirst
这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,如果为空集合返回 null。
List<Integer> list = Arrays.asList(1,2,3);
Optional<Integer> first = list.stream().findFirst(); // [1]
Optional 可以看做一个容器,它可能含有某值,为空抛异常。使用它的目的是尽可能避免 NullPointerException。
3.reduce
之前写的这篇文章中讲到了前端 ES6中的 reduce,和 stream 中的 reduce 十分相似。
主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
这里演示一个例子:
// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
4.match
拿 anyMatch 举例吧,anyMatch 和 noneMatch 类似,anyMatch 就是只有有一个满足条件就返回 true
List<Integer> list = Arrays.asList(1,2,3);
Boolean has = list.stream().anyMatch(item -> item == 1); // true
更深入的可以看一下这篇Streams 的幕后原理。
网友评论