java Stream API 用法分析与示例

前言

Java8 以来推出的 Stream API,让 流式编程 广泛应用了起来。这在某些场景下极大的简化了我们的代码,本章节对 StreamAPI 做一个简单的梳理介绍,并给出具体的 demo 实例,旨在理解其运用

版本

JDK11

创建

创建一个 Stream 的方法有很多,简单列举几种

Collection#stream()

	default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

Collection 接口提供的 default方法,允许 Collection 转换为 Stream,也是我们最常用的方法

演示 demo

	@Test
    public void test1() {
        new ArrayList<Integer>() {
            {
                add(1);
                add(2);
            }
        }.stream();
    }

Stream.of

	public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

Stream 接口提供的 静态方法,接受 T数组 参数转换为 Stream 返回

BaseStream
BaseStream 下提供了形如 IntStream DoubleStream 等类,分别维护对应 数据类型Stream,其下也会提供各自特色的 API
比如 IntStream.range(int startInclusive, int endExclusive) 把指定(左开右开)区间内的 Integer 封装成 StreamIntStream.rangeClosed(int startInclusive, int endInclusive) 把指定(左开右闭)区间内的 Integer 封装成 Stream

演示 demo

	@Test
    public void test1() {
        IntStream.of(1, 2, 3).forEach(System.out::println);
        Stream.of("1", "2").forEach(System.out::println);
        IntStream.range(1, 5).forEach(System.out::println);
        IntStream.rangeClosed(1, 5).forEach(System.out::println);
    }

Stream.generate

	public static<T> Stream<T> generate(Supplier<? extends T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }

接受一个 Supplier,生成对应的 Stream,但是注意:生成的是个 无限流,需要自行处理,具体见 demo

关于 Supplier 及相关的 函数式接口,可以阅读下面文章

常用函数式接口的解读及使用

演示 demo

	@Test
    public void test2() {
        // 无限流,必须以 limit 等方式限制长度
        Stream.generate(new Random()::nextInt).limit(3).forEach(System.out::println);
        IntStream.generate(new Random()::nextInt).limit(2).forEach(System.out::println);
    }

注意对 无限流 的限制处理

Stream.iterate

	public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        Objects.requireNonNull(f);
        Spliterator<T> spliterator = new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE,
               Spliterator.ORDERED | Spliterator.IMMUTABLE) {
            T prev;
            boolean started;

            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                Objects.requireNonNull(action);
                T t;
                if (started)
                    t = f.apply(prev);
                else {
                    t = seed;
                    started = true;
                }

				// 将上一轮的 t 赋值给 prev
                action.accept(prev = t);
                return true;
            }
        };
        return StreamSupport.stream(spliterator, false);
    }

指定一个 seed 执行 f(seed),在以 f(seed) 的结果 t,执行 f(t) 即 f(f(seed)) ,返回对应的 无限流

还提供了一个 重载 的方法

	public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) {
        Objects.requireNonNull(next);
        Objects.requireNonNull(hasNext);
        Spliterator<T> spliterator = new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE,
               Spliterator.ORDERED | Spliterator.IMMUTABLE) {
            T prev;
            boolean started, finished;

            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                Objects.requireNonNull(action);
                if (finished)
                    return false;
                T t;
                if (started)
                    t = next.apply(prev);
                else {
                    t = seed;
                    started = true;
                }

				// 当上一轮的结果 t 不满足 hasNext 时,停止
                if (!hasNext.test(t)) {
                    prev = null;
                    finished = true;
                    return false;
                }
                action.accept(prev = t);
                return true;
            }

            // 略
            
        };
        return StreamSupport.stream(spliterator, false);
    }

该方法提供一个 Predicate<? super T> hasNext 参数,用于判断 next(t) 的结果是否有效。换句话说,提供了一个可限制 无限流 的条件

演示 demo

	@Test
    public void test3() {
        // 无限流,必须以 limit 等方式限制长度
        Stream.iterate("1", i -> i + i).limit(3).forEach(System.out::println);
        
        // 添加了打断无限流的条件,因此可以不 limit
        Stream.iterate("2", i -> i.length() < 3, i -> i + i).forEach(System.out::println);
    }

结果:
1
11
1111
2
22 // 长度大于 3 时停止

过滤

提供了大量比如 filter skip limit distinct 等方法,直接看 demo 体会

	@Test
    public void test4() {
        Stream.of(1, 2, 2, 3, 3, 4)
                // 条件过滤
                .filter(i -> i > 1)
                // 去重
                .distinct()
                // 限制个数
                .limit(2)
                // 跳过
                .skip(1)
                .forEach(System.out::println);
	}

结果:3

map 和 flatMap

它们都是对 Stream 元素的转换,前者将 目标对象 转换成 其他类型 的对象,而后者是将 目标对象 转换成 其他类型对象对应的 Stream

map 方法

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

转换显然由 Function<? super T, ? extends R> mapper 实现

flatMap 方法

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

转换成了对应的 Stream

示例 demo

	@Test
    public void test6() {
        // map 后的本质是个 Stream<String[]>
        Stream.of("aa", "bb", "cc")
                .map(i -> i.split(""))
                .forEach(System.out::println);

        System.out.println("=================");

        // flatMap 后的本质是个 Stream<String>
        Stream.of("aa", "bb", "cc")
                .flatMap(i -> Stream.of(i.split("")))
                .forEach(System.out::println);
    }
    
结果:
[Ljava.lang.String;@24a35978
[Ljava.lang.String;@16f7c8c1
[Ljava.lang.String;@2f0a87b3
=================
a
a
b
b
c
c

排序

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

对元素进行排序,如果目标类不是一个 Comparable,可以传入自定义的 Comparator

示例 demo

	@Test
    public void test7() {
        class User {
            String id;

            public User(String id) {
                this.id = id;
            }

            @Override
            public String toString() {
                return "User{" +
                        "id='" + id + '\'' +
                        '}';
            }
        }

        Stream.of(new User("1"), new User("2"))
                .sorted((a, b) -> Integer.parseInt(a.id) - Integer.parseInt(b.id))
                .forEach(System.out::println);
    }

结果:
User{id='1'}
User{id='2'}

终止

Stream懒加载 的,也就是说,这之前的所有操作可以认为是 预定义,并没有被真正执行,直到调用 终止 方法,才会真正执行计算。该类方法有 allMatch anyMatch noneMatch findFirst findAny count max min toArray forEach forEachOrdered

我们结合具体示例 demo 说明

	@Test
    public void test8() {
        // 是否全部匹配
        System.out.println(Stream.of("a", "b", "c").allMatch(i -> i instanceof String)); // true
        // 至少匹配一个
        System.out.println(Stream.of("a", "b", "c").anyMatch(i -> "a".equals(i)));  // true
        // 全不匹配
        System.out.println(Stream.of("a", "b", "c").noneMatch(i -> "d".equals(i)));  // true

        System.out.println("========================================");

        // 获取第一个,返回的是个 Optional
        System.out.println(Stream.of("a", "b", "c").findFirst().get()); // a
        // 任意获取一个,返回的是个 Optional
        System.out.println(Stream.of("a", "b", "c").findAny().get());

        System.out.println("========================================");

        // 计数
        System.out.println(Stream.of("a", "b", "c").count()); // 3

        System.out.println("========================================");

        // 最大值,允许传入自定义的 Comparator
        System.out.println(Stream.of(1, 2, 3).max(Integer::compareTo).get()); // 2
        // 最小值,允许传入自定义的 Comparator
        System.out.println(Stream.of(1, 2, 3).min(Integer::compareTo).get()); // 1

        System.out.println("========================================");

        // Stream 转换为 Array
        for (Integer s : Stream.of(1, 2, 3).toArray(Integer[]::new)) {
            System.out.print(s);
        }
        System.out.println();

        System.out.println("========================================");

        // unordered 无序遍历输出
        Stream.of(1, 2, 3, 4, 5, 6).unordered().parallel().forEach(System.out::print);

        // forEachOrdered 和 collect 都能保证有序输出
        Stream.of(1, 2, 3, 4, 5, 6).unordered().parallel().forEachOrdered(System.out::print);
        System.out.println();
        Stream.of(1, 2, 3, 4, 5, 6).unordered().parallel().collect(Collectors.toList()).forEach(System.out::print);

    }

reduce

Optional<T> reduce(BinaryOperator<T> accumulator);
 T reduce(T identity, BinaryOperator<T> accumulator);

允许设置一个初始值 identity,作为参数与对应元素执行 accumulator.apply 方法

示例 demo

	@Test
    public void test9() {
        // 1 + 2 + 3 = 6
        System.out.println(Stream.of(1, 2, 3).reduce((a, b) -> a + b).get());

        // 6 + 1 + 2 + 3 = 12
        System.out.println(Stream.of(1, 2, 3).reduce(6, (a, b) -> a + b));
	}

Collectors

Collectors 提供了大量的方法,比如 转换 成对应的数据结构,比如 toList toSet toMap toCollection toConcurrentMap 等,同时也提供了 统计 相关方法比如 countig summingInt averagingInt summarizingInt 等方法,还有 分组 groupingBy分区 partitioningBy映射 mapping 等方法

转换

其中 toList toSet toCollection 方法的使用比较简单

	Stream.of(1, 2, 3).collect(Collectors.toList()).forEach(System.out::println);
    Stream.of(1, 2, 3).collect(Collectors.toSet()).forEach(System.out::println);
    Stream.of(1, 2, 3).collect(Collectors.toCollection(ArrayList::new)).forEach(System.out::println);

toMap 涉及到 KV 键值对的转换,因此 toMap 有如下三个重载方法

	public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return new CollectorImpl<>(HashMap::new,
                                   uniqKeysMapAccumulator(keyMapper, valueMapper),
                                   uniqKeysMapMerger(),
                                   CH_ID);
    }

	// 对于重复的 key,会抛出 duplicateKeyException 异常
	private static <K, V, M extends Map<K,V>>
    BinaryOperator<M> uniqKeysMapMerger() {
        return (m1, m2) -> {
            for (Map.Entry<K,V> e : m2.entrySet()) {
                K k = e.getKey();
                V v = Objects.requireNonNull(e.getValue());
                V u = m1.putIfAbsent(k, v);
                if (u != null) throw duplicateKeyException(k, u, v);
            }
            return m1;
        };
    }

	// 允许自定义重复 key 的处理策略
	public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {

		// 默认转换为 HashMap
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }

	// 指定转换 Map 类型,比如 LinkedHashMap 等
	public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                             Function<? super T, ? extends U> valueMapper,
                             BinaryOperator<U> mergeFunction,
                             Supplier<M> mapFactory) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
    }

分别支持我们自定义 KV 映射策略,重复key 的解决策略和具体的 Map类型

示例 demo

	@Test
    public void test10() {

        Stream.of(1, 2, 3)
                .collect(Collectors.toMap(k -> k, v -> v))
                .forEach((k, v) -> System.out.println(k +","+ v));

        // 重复的 key 拼接对应的 value
        Stream.of(1, 1, 3)
                .collect(Collectors.toMap(String::valueOf, String::valueOf, (oldV, newV) -> oldV +""+ newV))
                .forEach((k, v) -> System.out.println(k +","+ v));

        // 指定 LinkedHashMap
        Stream.of(1, 1, 3)
                .collect(Collectors.toMap(String::valueOf, String::valueOf, (oldV, newV) -> oldV +""+ newV, LinkedHashMap::new))
                .forEach((k, v) -> System.out.println(k +","+ v));
                
    }

统计

统计相关直接看示例 demo

	@Test
    public void test10() {
    
        // 计数
        System.out.println(Stream.of(1, 2, 3).collect(Collectors.counting()));

        // 求和
        System.out.println(Stream.of(1, 2, 3).collect(Collectors.summingInt(Integer::intValue)));
        
        // 求平均数
        System.out.println(Stream.of(1, 2, 3).collect(Collectors.averagingInt(Integer::intValue)));

        // IntSummaryStatistics 封装了各种统计数据
        IntSummaryStatistics collect = Stream.of(1, 2, 3).collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println(collect.getAverage());
        System.out.println(collect.getMax());

        // 指定拼接符号、前缀、后缀
        System.out.println(Stream.of("a", "b", "c")
                .collect(Collectors.joining(",", ">>", "<<")));

        // 获取最大值,以指定 Collector 排序,返回的是个 Optional
        System.out.println(Stream.of(1, 2, 3).collect(Collectors.maxBy(Integer::compareTo)).get());
        // 获取最小值,以指定 Collector 排序,返回的是个 Optional
        System.out.println(Stream.of(1, 2, 3).collect(Collectors.minBy(Integer::compareTo)).get());

    }

分组、分区、映射

这几个方法可以且经常组合使用,完成一些复杂的逻辑

分组 groupingBy

	// 默认连接 toList
	public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }

	// 默认以 HashMap 存储分组数据
	public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

	// 可以指定 downstream 和 mapFactory
	public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) {
        
        // 略
    
    }

提供了三个重载方法,classifier 指定 分组属性mapFactory 即承载分组数据的 Map类型downstream 可以理解为链接一个 Collector 操作,比如 toList partitioningBy counting 等等

分区 partitioningBy

	// 默认连接 toList
	public static <T>
    Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
        return partitioningBy(predicate, toList());
    }

	public static <T, D, A>
    Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
                                                    Collector<? super T, A, D> downstream) {
        
        // 略
    
    }

提供两个重载方法,以 predicate 分为 true false 两组,同样可以指定一个 downstream

映射 mapping

	public static <T, U, A, R>
    Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                               Collector<? super U, A, R> downstream) {
        BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
        return new CollectorImpl<>(downstream.supplier(),
                                   (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
                                   downstream.combiner(), downstream.finisher(),
                                   downstream.characteristics());
    }

指定 mapper 定义映射逻辑,同时必须指定一个 downstream

示例 demo

	@Test
    public void test11() {
        class Book {
            int id;
            int type;

            public Book(int id, int type) {
                this.id = id;
                this.type = type;
            }

            @Override
            public String toString() {
                return "Book{" +
                        "id=" + id +
                        ", type=" + type +
                        '}';
            }
        }

        List<Book> list = new ArrayList<>() {
            {
                add(new Book(1, 1));
                add(new Book(2, 2));
                add(new Book(3, 2));
            }
        };

        // 以 type 分组
        /**
         * 结果
         * 1:[Book{id=1, type=1}]
         * 2:[Book{id=2, type=2}, Book{id=3, type=2}]
         */
        list.stream()
                .collect(Collectors.groupingBy(b -> b.type))
                .forEach((k, v) -> System.out.println(k +":"+ v));

        // 分组后连接一个 counting 用于计数
        /**
         * 结果
         * 1:1
         * 2:2
         */
        list.stream()
                .collect(Collectors.groupingBy(b -> b.type, Collectors.counting()))
                .forEach((k, v) -> System.out.println(k +":"+ v));

        System.out.println("=================================");

        // 分区 type == 1 (true)和 type != 1 (false)
        /**
         * 结果
         * false:[Book{id=2, type=2}, Book{id=3, type=2}]
         * true:[Book{id=1, type=1}]
         */
        list.stream()
                .collect(Collectors.partitioningBy(i -> i.type == 1))
                .forEach((k, v) -> System.out.println(k +":"+ v));

        // 分区后连接 groupingBy,以 id 分组
        /**
         * 结果
         * false:{2=[Book{id=2, type=2}], 3=[Book{id=3, type=2}]}
         * true:{1=[Book{id=1, type=1}]}
         */
        list.stream()
                .collect(Collectors.partitioningBy(i -> i.type == 1
                        , Collectors.groupingBy(i -> i.id)))
                .forEach((k, v) -> System.out.println(k +":"+ v));

        // 分区 -> 映射 -> toList
        /**
         * 结果
         * false:[Book{id=10, type=10}, Book{id=10, type=10}]
         * true:[Book{id=10, type=10}]
         */
        list.stream()
                .collect(Collectors.partitioningBy(i -> i.type == 1
                        , Collectors.mapping(i -> new Book(10, 10)
                            , Collectors.toList())))
                .forEach((k, v) -> System.out.println(k +":"+ v));
    }

连接

提供了用于连接两个 Stream 的静态方法 concat

示例 demo

	@Test
    public void test12() {
        System.out.println(Stream.concat(Stream.of(1, 2), Stream.of(3, 4))
                .reduce(5, (a, b) -> a + b));
    }
	// 5 + 1 + 2 + 3 + 4 = 15

总结

可以看到,Stream API 相对来说还是十分庞大的,本文也只是介绍了部分方法的使用而已。在平时的开发工作中,Stream API 的使用也十分的频繁,因此掌握常用的方法时十分有必要的

参考

若想要了解更多更详细的 Stream API 相关知识,可以参考下文

【小家java】java8新特性之—Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值