Stream
java8新添加了一个特性:流Stream。Stream让开发者能够以一种声明的方式处理数据源(集合、数组等),它专注于对数据源进行各种高效的聚合操作(aggregate operation)和大批量数据操作 (bulk data operation)。
Stream API将处理的数据源看做一种Stream(流),Stream(流)在Pipeline(管道)中传输和运算,支持的运算包含筛选、排序、聚合等,当到达终点后便得到最终的处理结果。
关键概念
- 元素:Stream是一个来自数据源的元素队列,Stream本身并不存储元素。
- 数据源(Stream的来源):包括集合,数组,I/Ochannel,generator(发生器)等。
- 聚合操作:类似SQL中的Filter,map,find,match,sorted等操作。
- 管道运算:Stream在Pipline中运算后返回Stream对象本身,这样多个操作串联成一个Pipeline,并形成fluent风格的代码。这种方式可以优化操作,如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代 不同于java8以前对集合的遍历方式(外部迭代),Stream API采用访问者模式(Visitor)实现了内部迭代。
- 并行运算 Stream API支持串行(stream() )或并行(parallelStream() )的两种操作方式。
Stream API的特点:
- Stream API的使用和同样是java8新特性的lambda表达式密不可分,可以大大提高编码效率和代码可读性。
- Stream API提供串行和并行两种操作,其中并行操作能发挥多核处理器的优势,使用fork/join的方式进行并行操作以提高运行速度。
- Stream API进行并行操作无需编写多线程代码即可写出高效的并发程序,且通常可避免多线程代码出错的问题。
其实Stream可以分为并行和串行两种流方式,从数据源(集合,数组等)获取到元素队列(即Stream流)后,对这些元素队列进行聚合操作,聚合操作本身是返回一个流对象,这样又可以对新的流对象进行聚合操作,这样的多个聚合操作一起组成了我们的管道运算。这就是Stream流的基本概念。
入门demo
对一个数组进行筛选操作,来初步的对StreamAPI有一个初步的了解。
public class Java8Tester {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().filter(n -> n > 2).forEach(System.out::println);
}
}
StreamAPI经典接口
Stream的生成
串行流
stream()并行流
parallelStream()在使用层次上,串行流和并行流并没有区别,只是在底层实现上,串行流
stream()是串行进行处理的,而parallelStream是并行进行处理的,而正是因为并行处理,所以对集合的遍历无序的。
forEach()
forEach不仅在集合中有这个接口,在Stream中也有这个接口,这个接口会接受一个Consumer对象,无返回值。我们可以通过lambda表达式来对元素进行一个操作。
public class Java8Tester {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().forEach((n) -> System.out.println(n));
list.forEach((n) -> System.out.println(n));
}
}
map()和flatMap()
map()和flatMap()的参数都是一个Function接口,即函数式接口,并且返回一个Stream对象,都会对元素队列进行一个重新的操作。
比如,**map()**对元素队列进行一个简单的绝对值操作。
public class Java8Tester {
public static void main(String[] args){
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
numbers.stream().map(n -> Math.abs(n)).forEach(n -> System.out.println("Element abs: " + n));
}
}
**flatMap()**能够将一个二维的集合映射成一个一维的集合,比map()方法拥有更高的映射深度。
public class Java8Tester {
public static void main(String[] args)
{
List<String> numbers = Arrays.asList("a b", "c d", "e f");
numbers.stream().map(item -> Arrays.stream(item.split(" "))).forEach(n -> n.forEach(System.out::println));
numbers.parallelStream().map(item -> Arrays.stream(item.split(" "))).forEach(System.out::println);
}
}
filter()
filter()方法常用于过滤,它的参数为Predicate对象,返回值为一个Stream对象。
public class Java8Tester {
public static void main(String[] args)
{
List<String> numbers = Arrays.asList("a", "c", "e");
numbers.parallelStream().filter(n -> n.equals("a")).forEach(System.out::println);
}
}
reduce()
reduce操作又称为折叠操作,可以将元素队列中的所有只合为一个。
reduce()方法参数为BinaryOperator类型的累加器(它接受两个类型相同的参数,返回值类型跟参数类型相同),返回一个Optional对象。
public class Java8Tester {
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Integer integer = numbers.stream().reduce((a, b) -> a + b).get();
System.out.println(integer);
}
}
collect()
collect()方法的参数为一个java.util.stream.Collector类型对象,返回值为传入的参数类型。
可以用java.util.stream.Collectors工具类提供的静态方法来生成,Collectors类实现很多的归约操作,如Collectors.toList()、Collectors.toSet()、Collectors.joining()(joining适用于字符串流)等。
public class Java8Tester {
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> list = numbers.stream().map(n -> n + 1).collect(Collectors.toList());
}
}
collect()方法可以让我们对流操作后返回我们的集合对象。
summaryStatistics()
summaryStatics()方法会生成一个summaryStatics对象,然后这个对象中有很多快捷的关于数学计算的函数,可以很方便的提供我们使用,但是Stream流对象并没有提供这个方法。
我们在使用时一般是通过maoToInt,mapToLong,mapToDouble生成我们对应的数值流,再使用数值流生成我们的summarStistics对象。
栗子
public class Java8Tester {
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
IntSummaryStatistics summaryStatistics = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Max:" + summaryStatistics.getMax());
System.out.println("Sum:" + summaryStatistics.getSum());
System.out.println("Count:" + summaryStatistics.getCount());
}
}
Match()
判断是否含有匹配项,传入一个predicate对象,返回一个boolean值。它有三个类似方法,分别是noneMatch,allMatch,anyMatch。分别为没有一个满足条件,所有都满足条件,任意一个满足条件时返回true。
栗子
public class Java8Tester {
public static void main(String[] args)
{
List<String> numbers = Arrays.asList("张三","李四");
boolean a = numbers.stream().anyMatch((x) -> x.equals("张三"));
boolean b = numbers.stream().allMatch((x) -> x.equals("张三"));
boolean c = numbers.stream().noneMatch((x) -> x.equals("张三"));
System.out.println("=======" + a);
System.out.println("=======" + b);
System.out.println("=======" + c);
}
}
其他接口
Stream API还有一些其它的方法,比如:
limit() 获取指定数量的流
sorted() 对流进行排序
distinct() 去重
skip() 跳过指定数量的元素
peek() 生成一个包含原Stream的所有元素的新Stream,并指定消费函数
count() 计算元素数量
…
注意
Stream中的操作从概念上讲分为中间操作和终端操作:
- 中间操作:例如peek()方法提供Consumer(消费)函数,但执行peek()方法时不会执行Consumer函数,而是等到流真正被消费时(终端操作时才进行消费)才会执行,这种操作为中间操作;
- 终端操作:例如forEach()、collect()、count()等方法会对流中的元素进行消费,并执行指定的消费函数(peek方法提供的消费函数在此时执行),这种操作为终端操作。