一.什么是 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 }