java8新添加了一个特性:流Stream。Stream让开发者能够以一种声明的方式处理数据源(集合、数组等),它专注于对数据源进行各种高效的聚合操作(aggregate operation)和大批量数据操作 (bulk data operation)。
Stream API将处理的数据源看做一种Stream(流),Stream(流)在Pipeline(管道)中传输和运算,支持的运算包含筛选、排序、聚合等,当到达终点后便得到最终的处理结果。
我们来看一个简单的示例,统计整数数组中正数的个数:
1、在java8之前:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
long count = 0;
for(Integer number: numbers)
{
if(number > 0)
{
count++;
}
}
System.out.println("Positive count: " + count);
}
2、在java8之后:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
long count = numbers.parallelStream().filter(i -> i>0).count();
System.out.println("Positive count: " + count);
}
可以看到,上例中,使用filter()方法对数组进行了过滤,使用count()方法对过滤后的数组进行了大小统计,且使parallelStream()方法为集合创建了并行流,自动采用并行运算提高速度。在更复杂的场景,还可以用forEach()、map()、limit()、sorted()、collect()等方法进行进一步的流运算。
本节以典型场景为例,列出Stream API常用接口的用法,并附上相应代码。
需要说明的是,Stream API中存在很多方法重载,同名方法本文中可能仅列举一个,请读者注意~
java8 Stream API支持串行或并行的方式,可以简单看下jdk1.8 Collection接口的源码(注释只截取部分):
/**
* @return a sequential {@code Stream} over the elements in this collection
* @since 1.8
*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* @return a possibly parallel {@code Stream} over the elements in this collection
* @since 1.8
*/
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
可以看出,在集合类的接口(Collection)中,分别用两种方式来生成:
应该注意的是,使用parallelStream()生成并行流后,对集合元素的遍历是无序的。
简单看下forEach()方法的源码(注释只截取部分):
/**
* Performs an action for each element of this stream.
*/
void forEach(Consumer<? super T> action);
forEach()方法的参数为一个Consumer(消费函数,一个函数式接口)对象,forEach()方法用来迭代流中的每一个数据,例如:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
numbers.stream().forEach(n -> System.out.println("List element: " + n));
}
上例中,对数组的每个元素进行串行遍历,并打印每个元素的值。
ps:集合的顶层接口Iterable中也投forEach方法,可以直接对数组元素进行遍历:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
numbers.forEach(n -> System.out.println("List element: " + n));
}
当然用Strem API的好处不仅仅是遍历~~~
简单看下map()方法的源码(注释只截取部分):
/**
* Returns a stream consisting of the results of applying the given function to the elements of this stream.
* @param The element type of the new stream
* @param mapper a non-interfering,
* stateless
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map()方法的参数为Function(函数式接口)对象,map()方法将流中的所有元素用Function对象进行运算,生成新的流对象(流的元素类型可能改变)。举例如下:
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));
}
上例中,用map()方法计算了所有数组元素的绝对值并生成了一个新的流,然后再用forEach()遍历打印。
简单看下flatMap()方法的源码(省略注释):
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
显然,跟map()方法不同的是,Function函数的返回值类型是Stream extends R>类型,而不是R类型,即Function函数返回一个Stream流,这样flatMap()能够将一个二维的集合映射成一个一维的集合,比map()方法拥有更高的映射深度(此处可能有一点绕,可结合例子理解),作个简单示例如下:
有一个字符串数组:
List<String> list = Arrays.asList("1 2", "3 4", "5 6");
其有三个元素,每个元素有两个数组并用空格隔开,如果每个元素以空格分割成2个元素,并遍历打印这6个元素,
用flatMap()方法如下:
list.stream().flatMap(item -> Arrays.stream(item.split(" "))).forEach(System.out::println);
而用map()方法:
list.stream().map(item -> Arrays.stream(item.split(" "))).forEach(n ->n.forEach(System.out::println));
可见,用map()方法,返回了一个“流中流”,需要在每个Stream元素遍历时,再加一层forEach进行遍历。
简单看下filter()方法的源码(注释只截取部分):
/**
* Returns a stream consisting of the elements of this stream that match the given predicate.
*
* This is an intermediate operation.
*
* @param predicate a non-interfering,
* stateless
* predicate to apply to each element to determine if it should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
filter()方法的参数为Predicate(函数式接口)对象,再lambda表达式的讲解中我们提到过这个接口,一般用它进行过滤。正如第二章中示例:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
long count = numbers.parallelStream().filter(i -> i>0).count();
System.out.println("Positive count: " + count);
}
用filter方法很容易过滤出整数数组中的自然数。
reduce操作又称为折叠操作,用于将流中的所有值合成一个。reduce()方法的源码(不提供计算初始值的reduce方法)(省略注释):
Optional<T> reduce(BinaryOperator<T> accumulator);
reduce()方法参数为BinaryOperator类型的累加器(它接受两个类型相同的参数,返回值类型跟参数类型相同),返回一个Optional对象。
实际上,Stream API中的mapToInt()方法返回的IntStream接口有类似的 average()、count()、sum()等方法就是做reduce操作,类似的还有mapToLong()、mapToDouble() 方法。当然,我们也可以用reduce()方法来自定义reduce操作。例如我们用reduce()方法来进行整数数组求和操作:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, -1, 4, 5, 1);
Integer total = numbers.stream().reduce((t, n) -> t + n).get();
System.out.println("Total: " + total);
}
上例中利用reduce()方法结合lambda表达式轻易的实现了数组的求和功能。
简单看下collect()方法的源码(注释只截取部分):
/**
* @param the type of the result
* @param the intermediate accumulation type of the {@code Collector}
* @param collector the {@code Collector} describing the reduction
* @return the result of the reduction
*/
<R, A> R collect(Collector<? super T, A, R> collector);
collect()方法的参数为一个java.util.stream.Collector类型对象,可以用java.util.stream.Collectors工具类提供的静态方法来生成,Collectors类实现很多的归约操作,如Collectors.toList()、Collectors.toSet()、Collectors.joining()(joining适用于字符串流)等。看一个简单示例:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
List<Integer> abss = numbers.stream().map( n -> Math.abs(n)).collect(Collectors.toList());
System.out.println("Abs list: " + abss);
}
上例中,用map()方法生成新的流,再用collect()方法返回原数组的绝对值数组。
其实summaryStatistics()方法并不是Stream接口的方法,而是Stream API采用mapToInt()、mapToLong()、mapToDouble()三个方法分别生成IntStream 、LongStream 、DoubleStream 三个接口类型的对象,这个方法的参数分别为3个函数式接口ToIntFunction、ToLongFunction、ToDoubleFunction,使用时可以用lambda表达式计算返回对应的int、long、double类型即可,简单看下这三个方法的源码(省略注释):
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
IntStream 、LongStream 、DoubleStream 三个接口类型都有一个summaryStatistics()方法,其中,
1、IntStream 的方法是:
IntSummaryStatistics summaryStatistics();
2、LongStream 的方法是:
LongSummaryStatistics summaryStatistics();
3、DoubleStream 的方法是:
DoubleSummaryStatistics summaryStatistics();
在IntSummaryStatistics、LongSummaryStatistics 、DoubleSummaryStatistics 三个接口类型(位于java.util包下)中,都有诸如统计数量、最大值、最小值、求和、平均值等方法(方法名和返回类型可能不同),利用这些方法我们可以方便的进行数值统计。以IntSummaryStatistics工具包 为例:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Max : " + stats.getMax());
System.out.println("Min : " + stats.getMin());
System.out.println("Sum : " + stats.getSum());
System.out.println("Average : " + stats.getAverage());
System.out.println("Count : " + stats.getCount());
}
Stream API还有一些其它的方法,比如:
limit() 获取指定数量的流
sorted() 对流进行排序
distinct() 去重
skip() 跳过指定数量的元素
peek() 生成一个包含原Stream的所有元素的新Stream,并指定消费函数
count() 计算元素数量
…
感兴趣的读者可以阅读源码,读到这里已经很容易理解了,本文不再赘述。
Stream中的操作从概念上讲分为中间操作和终端操作:
要理解中间操作和终端操作的概念,防止埋坑~