一.什么是 Stream 流
Java8开始,得益于Lambda所带来的函数式编程,引入一个全新的Stream概念,用于解决已有集合类库的弊端。
- Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素:特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源:流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
- 和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
二.传统方式与Stream流对比
1.传统方式:
1 public class Demo01List {
2
3 public static void main(String[] args) {
4
5 List<String> list = new ArrayList<>();
6
7 list.add("张无忌");
8 list.add("周芷若");
9 list.add("赵敏");
10 list.add("张强");
11 list.add("张三丰");
12
13 //传统方式:找出姓张,姓名长度大于2的人,左后打印出来
14 List<String> listA = new ArrayList<>();
15 for (String s : list) {
16 if (s.startsWith("张")) {
17 listA.add(s);
18 }
19 }
20
21 List<String> listB = new ArrayList<>();
22 for (String s : listA) {
23 if (s.length() > 2) {
24 listB.add(s);
25 }
26 }
27
28 for (String s : listB) {
29 System.out.println(s);
30 }
31
32 }
33 }
结果:
1 张无忌
2 张三丰
2.使用Stream流的方式:
1 public class Demo01Stream {
2
3 public static void main(String[] args) {
4
5 List<String> list = new ArrayList<>();
6
7 list.add("张无忌");
8 list.add("周芷若");
9 list.add("赵敏");
10 list.add("张强");
11 list.add("张三丰");
12
13 //Stream流的方式:找出姓张,姓名长度大于2的人,左后打印出来
14 list.stream()
15 .filter(name->name.startsWith("张"))
16 .filter(name->name.length()>2)
17 .forEach(name-> System.out.println(name));
18
19 }
20 }
结果:
1 张无忌
2 张三丰
结论:大大简化了代码
三.获取Stream流的两种方式
- 将集合转换为Stream流(只对单列集合):
- default Stream<E> stream()
- 将数组转换为Stream流:
- static <T> Stream<T> of(T... values)
1 public class Demo01GetStream {
2
3 public static void main(String[] args) {
4
5 //1.将集合转换为Stream流
6 //List
7 List<String> list = new ArrayList<>();
8 Stream<String> stream1 = list.stream();
9
10 //Set
11 Set<String> set = new HashSet<>();
12 Stream<String> stream2 = set.stream();
13
14 //Map
15 Map<String,String> map = new HashMap<>();
16 //获取键转换为Stream流
17 Set<String> keyset = map.keySet();
18 Stream<String> stream3 = keyset.stream();
19 //获取值转换为Stream流
20 Collection<String> values = map.values();
21 Stream<String> stream4 = values.stream();
22 //获取键值对的映射关系entrySet转换为Stream流
23 Set<Map.Entry<String,String>> entries = map.entrySet();
24 Stream<Map.Entry<String,String>> stream5 = entries.stream();
25
26 //2.将数组转换为Stream流
27 //可变参数
28 Stream<Integer> stream6 = Stream.of(1,2,3,4,5);
29 //固定数组
30 Integer[] arr = {1,2,3,4,5};
31 Stream<Integer> stream7 = Stream.of(arr);
32
33 }
34 }
四.Stream流中的常用方法
常用方法分为两种类型:
- 延迟方法:返回类型仍然是Stream接口自身类型的方法,支持链式调用(除了终结方法外,都是延迟方法)
- 终结方法:返回类型不再是Stream接口自身类型的方法,因此不能链式调用了,中介方法包括count和forEach方法。
1.终结方法:
逐一处理:forEach
- void forEach(consumer<? super T> action):该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。(终结方法)
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("张无忌");
7 list.add("周芷若");
8 list.add("赵敏");
9 list.add("张强");
10 list.add("张三丰");
11
12 list.stream().forEach((name)-> System.out.println(name));
13
14 /**
15 * 输出:
16 * 张无忌
17 * 周芷若
18 * 赵敏
19 * 张强
20 * 张三丰
21 */
22 }
23 }
统计个数方法:count
- long count():统计流中元素的个数。(终结方法)
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("1");
7 list.add("2");
8 list.add("3");
9 list.add("4");
10 list.add("5");
11
12 System.out.println(list.stream().count());
13
14 /**
15 * 输出:
16 * 5
17 */
18 }
19 }
2.延迟方法:
过滤:filter
- Stream<T> filter(Predicate<? super T> predicate):可以通过filter方法将一个流转换成另一个子集流。
- 注意:不会修改原来集合,而是会新生成一个集合
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("张无忌");
7 list.add("周芷若");
8 list.add("赵敏");
9 list.add("张强");
10 list.add("张三丰");
11
12 list.stream().filter((name) -> name.startsWith("张")).forEach((name) -> System.out.println(name));
13
14 System.out.println("---------------");
15 for (String s : list) {
16 System.out.println(s);
17 }
18 /**
19 * 输出:
20 * 张无忌
21 * 张强
22 * 张三丰
23 * ---------------
24 * 张无忌
25 * 周芷若
26 * 赵敏
27 * 张强
28 * 张三丰
29 */
30 }
31 }
注意:
Stream流特点:
- Stream流属于管道流,只能被消费一次。
- 前一个Stream流执行完毕,数据会传递给下一个流。这样前一个流就会关闭不能在对前一个流调用方法了。
- 报错:java.lang.IllegalStateException: stream has already been operated upon or closed
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("张无忌");
7 list.add("周芷若");
8 list.add("赵敏");
9 list.add("张强");
10 list.add("张三丰");
11
12 Stream<String> stream1 = list.stream();
13 Stream<String> stream2 = stream1.filter((name) -> name.startsWith("张"));
14 stream2.forEach((name) -> System.out.println(name));
15
16 //报错:java.lang.IllegalStateException: stream has already been operated upon or closed
17 //报错原因是stream1已经被使用过了
18 Stream<String> stream3 = stream1.filter((name) -> name.length() > 2);
19 stream3.forEach((name) -> System.out.println(name));
20
21 //报错原因stream2已经被使用过了
22 stream2.forEach((name) -> System.out.println("--" + name));
23 }
24 }
映射:map
- <R> Stream<R> map(Function<? super T, ? extends R> mapper):将一个流映射到另一个流中(将类型T转换为类型R)
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("1");
7 list.add("2");
8 list.add("3");
9 list.add("4");
10 list.add("5");
11
12 list.stream().map((s) -> Integer.parseInt(s)).forEach((i) -> System.out.println(i));
13
14 /**
15 * 输出:
16 * 1
17 * 2
18 * 3
19 * 4
20 * 5
21 */
22 }
23 }
取用前几个元素:limit
- Stream<T> limit(long maxSize):对流进行截取,只取用前几个元素。
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("1");
7 list.add("2");
8 list.add("3");
9 list.add("4");
10 list.add("5");
11
12 list.stream().limit(3).forEach((s) -> System.out.println(s));
13
14 /**
15 * 输出:
16 * 1
17 * 2
18 * 3
19 */
20 }
21 }
跳过前几个元素:skip
- Stream<T> skip(long n):跳过流的前几个元素,剩下的元素生成新流。
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("1");
7 list.add("2");
8 list.add("3");
9 list.add("4");
10 list.add("5");
11
12 list.stream().skip(3).forEach((s) -> System.out.println(s));
13
14 /**
15 * 输出:
16 * 4
17 * 5
18 */
19 }
20 }
组合:concat
- public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):Stream类中的静态方法,将两个流合并为一个新的流使用。
1 public class StreamTest {
2
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5
6 list.add("1");
7 list.add("2");
8 list.add("3");
9 list.add("4");
10 list.add("5");
11
12 Stream<String> stream1 = list.stream().limit(2);
13 Stream<String> stream2 = list.stream().skip(3);
14
15 Stream.concat(stream1, stream2).forEach((s) -> System.out.println(s));
16
17 /**
18 * 输出:
19 * 1
20 * 2
21 * 4
22 * 5
23 */
24 }
25 }
补充:来自B站字母哥课堂
调试Stream流