Java8新特性-Stream流

Stream

java8新添加了一个特性:流Stream。Stream让开发者能够以一种声明的方式处理数据源(集合、数组等),它专注于对数据源进行各种高效的聚合操作(aggregate operation)和大批量数据操作 (bulk data operation)。

Stream API将处理的数据源看做一种Stream(流),Stream(流)在Pipeline(管道)中传输和运算,支持的运算包含筛选、排序、聚合等,当到达终点后便得到最终的处理结果。

关键概念

  1. 元素:Stream是一个来自数据源的元素队列,Stream本身并不存储元素。
  2. 数据源(Stream的来源):包括集合,数组,I/Ochannel,generator(发生器)等。
  3. 聚合操作:类似SQL中的Filter,map,find,match,sorted等操作。
  4. 管道运算:Stream在Pipline中运算后返回Stream对象本身,这样多个操作串联成一个Pipeline,并形成fluent风格的代码。这种方式可以优化操作,如延迟执行(laziness)和短路( short-circuiting)。
  5. 内部迭代 不同于java8以前对集合的遍历方式(外部迭代),Stream API采用访问者模式(Visitor)实现了内部迭代。
  6. 并行运算 Stream API支持串行(stream() )或并行(parallelStream() )的两种操作方式。

Stream API的特点

  1. Stream API的使用和同样是java8新特性的lambda表达式密不可分,可以大大提高编码效率和代码可读性。
  2. Stream API提供串行和并行两种操作,其中并行操作能发挥多核处理器的优势,使用fork/join的方式进行并行操作以提高运行速度。
  3. 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流对象并没有提供这个方法。

我们在使用时一般是通过maoToIntmapToLongmapToDouble生成我们对应的数值流,再使用数值流生成我们的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值。它有三个类似方法,分别是noneMatchallMatchanyMatch。分别为没有一个满足条件,所有都满足条件,任意一个满足条件时返回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方法提供的消费函数在此时执行),这种操作为终端操作。


版权声明:本文为super1223原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。