简介
Stream是 Java 8新增加的类,用来补充集合类。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
Stream有以下特性及优点:
- 无存储。Stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
- 为函数式编程而生。对Stream的任何修改都不会修改背后的数据源,比如对Stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新Stream。
- 惰式执行。Stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
- 可消费性。Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
Stream的创建
在Java 8中,可以有多种方法来创建流。
1、通过已有的集合来创建流
在Java 8中,除了增加了很多Stream相关的类以外,还对集合类自身做了增强,在其中增加了stream方法,可以将一个集合类转换成流。
以上,通过一个已有的List创建一个流。除此以外,还有一个parallelStream方法,可以为集合创建一个并行流。List<String> strings = Arrays.asList("H", "e", "l", "Hello", "HelloWorld", "o");
Stream<String> stream = strings.stream();
这种通过集合创建出一个Stream的方式也是比较常用的一种方式。
2、通过Stream创建流
可以使用Stream类提供的方法,直接返回一个由指定元素组成的流。
如以上代码,直接通过of方法,创建并返回一个Stream。Stream<String> stream = Stream.of("h", "e", "l", "Hello", "w", "o");
Stream中间操作
map
map 方法用于映射每个元素到对应的结果
// 转换大小写
List<String> output = wordList.stream().map(String::toUpperCase).collect(Collectors.toList());
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().map( i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25
flatmap
flatmap
将多个Stream连接成一个Stream,这时候不是用新值取代Stream的值,与map有所区别,这是重新生成一个Stream对象。
List<String> words = new ArrayList<String>();
words.add("hello");
words.add("world");
public static Stream<Character> characterStream(String s){
List<Character> result = new ArrayList<>();
for (char c : s.toCharArray())
result.add(c);
return result.stream();
}
Stream<Stream<Character>> result = words.map(w -> characterStream(w));
Stream<Character> letters = words.flatMap(w -> characterStream(w));
如果使用的是map方法,返回的是[ [‘h’, ‘e’, ‘l’, ‘l’,’o’], [‘w’, ‘o’, ‘r’, ‘l’,’d’] 如果使用的是flatMap方法,返回的是[‘h’, ‘e’, ‘l’, ‘l’,’o’,’w’, ‘o’, ‘r’, ‘l’,’d’]
filter
filter 方法用于通过设置的条件过滤出元素。
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
//只留偶数
Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
//使用 filter 方法过滤掉空字符串
List<String> strings = Arrays.asList("Halo", "", "h", "H", "hello");
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
limit/skip
limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。
// 返回前四个元素
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
//3,2,2,3
sorted
sorted 方法用于对流进行排序。
Random random = new Random();
// 对10个随机数进行排序输出
random.ints().limit(10).sorted().forEach(System.out::println);
distinct
distinct主要用来去重,以下代码片段使用 distinct 对元素进行去重:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);
//3,2,7,5
Stream最终操作
Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)
最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
forEach
Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。
Random random = new Random();
// 随机输出10个整数
random.ints().limit(10).forEach(System.out::println);
Coun
count用来统计流中的元素个数。
List<String> strings = Arrays.asList("Hello", "World", "h","e", "l", "l", "o");
System.out.println(strings.stream().count());
//7
Collect
collect就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果:
List<String>strings = Arrays.asList("you", "are", "stupid", "ha", "ha","", "a");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
下图包含了所有的操作