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 操作是必要非充分条件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容