jdk1.8新特性之Stream使用详解

1、Stream简介

Stream作为Java 8的一大亮点,是对集合(Collection)、数组对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常编写并行代码很难且容易出错,但使用Stream API无需编写一行多线程的代码,就可以方便的写出高性能的并发程序。所以说,Java 8中首次出现的java.util.stream是一个函数式语言+多核时代综合影响的产物。

Stream是一组用来处理数组、集合的API

Java 8之所以费这么大功夫引入函数式编程,原因有二:

  • 代码简洁:函数式编程写出的代码简洁且意图明确,使用Stream接口让你从此告别for循环。
  • 多核友好:Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一个parallel()方法。

2、Stream特性

  • 不是数据结构,没有内部存储
  • 不支持索引访问
  • 延迟计算
  • 支持并行
  • 很容易生成数组或集合(List,Set)
  • 支持过滤,查找,转换,汇总,聚合等操作

3、Stream运行机制

Stream分为源Source、中间操作、终止操作。

  • 流的源可以是一个数组、一个集合、一个生成器方法、一个I/O通道等等。
  • 一个流可以有零个或者多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用。
  • 一个流只会有一个终止操作。
  • Stream只有遇到终止操作,它的源才开始执行遍历操作。

4、Stream的创建

  • 通过数组
Arrays.stream(T array)
Stream.of(T array)
    /**
     * 方式一: 通过数组生成
     */
    @Test
    public void testBuildStream1() {
        String[] strArr = {"a", "b", "c"};
        //static <T> Stream<T> of(T... values)  返回其元素是指定值的顺序排序流。
        Stream<String> stream1 = Stream.of(strArr);
        stream1.forEach(System.out::println);
        //static <T> Stream<T> of(T t) 返回包含单个元素的顺序 Stream 。
        Stream<String> stream2 = Stream.of("d");
        stream2.forEach(System.out::println);
    
        Stream<String> stream3 = Arrays.stream(strArr);
        stream3.forEach(System.out::println);
    }
  • 通过集合
    Collection.stream()
    Collection.parallelStream():获取的是并行操作的流
    /**
     * 方式二: 通过集合来生成
     */
    @Test
    public void testBuildStream2() {
        List<String> list = Arrays.asList("a", "b", "c", "d");
        Stream<String> stream1 = list.stream();
        stream1.forEach(System.out::println);
    
        // parallelStream(): 获取的是并行操作的流
        Stream<String> stream2 = list.parallelStream();
        stream2.forEach(System.out::println);
    }
  • 通过Stream.generate方法来创建
    /**
     * 方式三: 通过Stream.generate来生成
     */
    @Test
    public void testBuildStream3() {
        // Stream.generate : 返回无限顺序无序流,其中每个元素由提供的 Supplier 。 使用该方法的时候注意要结合limit来使用, 否则Stream中元素的个数是无限的
        Stream<Integer> generate = Stream.generate(() -> 1);
        generate.limit(10).forEach(System.out::println);
    }
  • 通过Stream.iterate方法来创建
    /**
     * 方式四: 通过Stream.iterate来生成
     */
    @Test
    public void testBuildStream4() {
        // Stream.iterate(T seed, UnaryOperator<T> f)  : 返回有序无限连续 Stream由函数的迭代应用产生 f至初始元素 seed ,产生 Stream包括 seed , f(seed) , f(f(seed)) ,等
        Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
        iterate.limit(10).forEach(System.out::println);
    }
  • 其它API创建

需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long> >、Stream<Double>,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的Stream。

Java 8 中还没有提供其它数值型Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。

    /**
     * 方式五: 其它方式创建Stream
     */
    @Test
    public void testBuildStream5() {
        String str = "abcdef";
        IntStream stream = str.chars();
        //stream.forEach(System.out::println);
        stream.forEach(x -> System.out.println((char) x));
    }

5、Stream常用API

5.1、中间操作

一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

过滤:filter

    @Test
    public void testFilter() {
        Arrays.asList(1, 2, 3, 4, 5, 6).stream().filter(x -> {
            System.out.println("filter里面的方法被执行了");
            return x % 2 == 0;
        }).forEach(System.out::println);
    }

去重:distinct

    // Stream中间操作distinct 去除Stream中的重复元素 使用的是Object.equals(Object)
    @Test
    public void testDistinct() {
        Arrays.asList(1, 2, 2, 4, 4, 6).stream().distinct().forEach(System.out::println);
    }

排序:sorted

    // Stream中间操作sorted 对Stream中的元素进行排序
    // sorted(): 按照自然顺序排序
    // sorted(Comparator<? super T> comparator) : 按照指定的比较器进行排序
    @Test
    public void testSorted() {
        Arrays.asList(6, 2, 5, 1, 4, 3).stream().sorted().forEach(System.out::print);
        System.out.println();
        Arrays.asList(6, 2, 5, 1, 4, 3).stream().sorted((a, b) -> a - b).forEach(System.out::print);
        System.out.println();
        Arrays.asList(6, 2, 5, 1, 4, 3).stream().sorted(Comparator.comparingInt(a -> a)).forEach(System.out::print);
        System.out.println();
        Arrays.asList(6, 2, 5, 1, 4, 3).stream().sorted(Integer::compare).forEach(System.out::print);
    }

合并流:concat

    // Stream中间操作concat 合并流
    // concat(Stream<? extends T> a, Stream<? extends T> b)
    // 创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。
    @Test
    public void testConcat() {
        Stream.concat(Arrays.asList("a","b","c").stream(), Arrays.asList("d","e","f").stream()).forEach(System.out::println);
    }

截取:limit、skip

    // Stream中间操作limit 截取
    // limit(long maxSize) : 返回由此流的元素组成的流,截短长度不能超过 maxSize 。
    @Test
    public void testLimit() {
        // 截取前2个原始
        Arrays.asList("a","b","c").stream().limit(2).forEach(System.out::println);
    }
    
    // Stream中间操作skip 截取
    // skip(long n) : 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
    @Test
    public void testSkip() {
        // 跳过第一个原始
        Arrays.asList("a","b","c").stream().skip(1).forEach(System.out::println);
    }

转换:map/flatMap/

    // Stream中间操作map 转换
    // map(Function<? super T,? extends R> mapper)
    @Test
    public void testMap() {
        Arrays.asList("a","b","c").stream().map(x -> x.toUpperCase()).forEach(System.out::println);
    }
    
    // Stream中间操作flatMap 转换
    // flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
    // flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
    @Test
    public void testFlatMap() {
        Stream<List<Integer>> inputStream = Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4), Arrays.asList(5, 6));
        Stream<Integer> stream = inputStream.flatMap(childList -> childList.stream());
        stream.forEach(System.out::println);
    }

其它:peek

    // Stream中间操作peek 查看
    @Test
    public void testPeek() {
        List<User> list = Arrays.asList(new User("zhangsan", 1), new User("lisi", 2));
        List<User> collect = list.stream().peek(x -> x.setName("lili")).collect(Collectors.toList());
        collect.stream().forEach(System.out::println);
    }

5.2、终止操作

一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。

循环:forEach

计算:min、max、count、average、sum

    // Stream终止操作min 求Stream中的最小值
    // min(Comparator<? super T> comparator): 根据提供的 Comparator返回此流的最小元素。
    @Test
    public void testMin() {
        Optional<Integer> min = Arrays.asList(6, 2, 5, 1, 4, 3).stream().min((a, b) -> a - b);
        if (min.isPresent()) {
            System.out.println(min.get());
        }
    }
    
    // Stream终止操作max 求Stream中的最大值
    // max(Comparator<? super T> comparator) : 根据提供的 Comparator返回此流的最大元素。
    @Test
    public void testMax() {
        Optional<Integer> max = Arrays.asList(6, 2, 5, 1, 4, 3).stream().max((a, b) -> a - b);
        if (max.isPresent()) {
            System.out.println(max.get());
        }
    }
    
    // Stream终止操作count 求Stream中的元素个数
    // count() : 返回此流中的元素数。
    @Test
    public void testCount() {
        long count = Arrays.asList(6, 2, 5, 1, 4, 3).stream().count();
        System.out.println(count);
    }
    
    // IntStream终止操作average()  求Stream中的元素的算术平均值
    @Test
    public void testAverage() {
        OptionalDouble average = Arrays.asList("6", "2", "5", "1", "4", "3").stream().mapToInt(Integer::parseInt).average();
        if (average.isPresent()) {
            System.out.println(average.getAsDouble());
        }
    }
    
    // IntStream终止操作sum()  返回此流中元素的总和。
    @Test
    public void testSum() {
        int sum = Arrays.asList("6", "2", "5", "1", "4", "3").stream().mapToInt(Integer::parseInt).sum();
        System.out.println(sum);
    }

匹配:anyMatch、allMatch、noneMatch、findFirst、findAny

    // Stream终止操作allMatch
    // allMatch(Predicate<? super T> predicate) : 返回此流的所有元素是否与提供的谓词匹配(所有的元素都满足返回true, 否则返回false)。
    @Test
    public void testAllMatch() {
        boolean b = Arrays.asList(6, 2, 5, 1, 4, 3).stream().allMatch(x -> x > 0);
        System.out.println(b);
    }
    
    // Stream终止操作anyMatch
    // anyMatch(Predicate<? super T> predicate) : 返回此流的任何元素是否与提供的谓词匹配(任何一个元素符合条件则返回true, 否则返回false)。
    @Test
    public void testAnyMatch() {
        boolean b = Arrays.asList(6, 2, 5, 1, 4, 3).stream().anyMatch(x -> x > 5);
        System.out.println(b);
    }
    
    // Stream终止操作noneMatch
    // noneMatch(Predicate<? super T> predicate) : 返回此流的元素是否与提供的谓词匹配。 (所有元素都不匹配则返回true, 否则返回false)。
    @Test
    public void testNoneMatch() {
        boolean b = Arrays.asList(6, 2, 5, 1, 4, 3).stream().noneMatch(x -> x > 5);
        System.out.println(b);
    }
    
    // Stream终止操作findFirst
    // findFirst()  : 返回描述此流的第一个元素的Optional如果流为空,则返回一个空的Optional 。
    @Test
    public void testFindFirst() {
        Optional<Integer> first = Arrays.asList(6, 2, 5, 1, 4, 3).stream().findFirst();
        if (first.isPresent()) {
            System.out.println(first.get());
        }
    }
    
    // Stream终止操作findAny
    // findAny() : 返回描述流的一些元素的Optional如果流为空,则返回一个空的Optional 。
    @Test
    public void testFindAny() {
        Optional<Integer> any = Arrays.asList(6, 2, 5, 1, 4, 3).stream().findAny();
        if (any.isPresent()) {
            System.out.println(any.get());
        }
    }

汇聚:reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于Integer sum = integers.reduce(0, (a, b) -> a+b); 或Integer sum = integers.reduce(0, Integer::sum);

也有没有起始值的情况,这时会把Stream的前面两个元素组合起来,返回的是 Optional。

    // Stream终止操作reduce
    @Test
    public void testReduce() {
        // 字符串连接,concat = "ABCD"
        String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
        System.out.println(concat);
        // 求最小值,minValue = -3.0
        double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
        System.out.println(minValue);
        // 求和,sumValue = 10, 有起始值
        int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
        System.out.println(sumValue);
        // 求和,sumValue = 10, 无起始值
        sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
        System.out.println(sumValue);
        // 过滤,字符串连接,concat = "ace"
        concat = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);
        System.out.println(concat);
    }

上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。

收集器:toArray、collect

    // Stream终止操作toArray、collect
    @Test
    public void testToArrayAndCollect() {
        List<Integer> collect = Arrays.asList(6, 2, 5, 1, 4, 3).stream().collect(Collectors.toList());
        System.out.println(collect);
    
        Object[] objects = Arrays.asList(6, 2, 5, 1, 4, 3).stream().toArray();
        Arrays.stream(objects).forEach(System.out::println);
    }

在对于一个Stream进行多次转换操作 (Intermediate 操作),每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是N(转换次数)个for循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是lazy的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

还有一种操作被称为short-circuiting。用以指:

对于一个intermediate操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream。

对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

当操作一个无限大的Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容