Java8新特性

282 阅读5分钟

1.方法引用

  • 类名::静态方法名

  • 引用名(对象名)::实例方法名

  • 类名::实例方法名

  • 构造方法引用

2.stream

流由️三部分组成

  • 零个或者多个操作

  • 终止操作

流操作的分类

  • 惰性求值

  • 及早求值

Collection提供了新的Stream()方法

  • 流不存储值,通过管道的方式获取值

  • 本质是函数式的,对流的操作会生成一个结果,不过并不会修改底层的数据源,集合可以做为流的底层数据源

  • 延迟查找,很多流操作(过滤,映射,排序)都可以实现延迟操作

集合和流的区别

  • 集合关注的是数据与数据存储本身,流关注的是则是对数据的计算.

  • 流与迭代器类似的一点: 流是无法重复使用和消费的

3.收集器 Collect o<T,A,R>

T 代表流中元素的类型

A 代表中间操作收集容器的类型

R 代表返回的结果类型

Supplier<A> supplier()

  • 返回一个用于中间收集的结果容器 A

BiConsumer<A, T> accumulator()

  • 累加器,不断的从流中遍历元素,然后将T累加到A当中

BinaryOperator<A> combiner()

  • 并发的,将多个线程所执行的部分结果合并到一起,将后面的合并到前面,其实是返回第一个(具体看下面源码分析)

Function<A, R> finisher()

  • 可选的方法, 完成器. 将不断累积的结果A转换为结果R(其实不难理解,A就是最后的结果R)

Set<Characteristics> characteristics()

4.枚举类Characteristics

CONCURRENT

  • 如果一个收集器是CONCURRENT的话,中间结果容器只会有唯一的一个,多个线程会同时操作这个结果容器;如果不加这个参数,那么如果有多少个线程,就会产生多少个中间结果容器,最后进行一个combiner的合并.

UNORDERED

  • 数据源是无序的就加这个参数

IDENTITY_FINISH

  • 加了这个参数不会调用 finisher()方法, 会讲中间结果进行强制类型转换,转换为结果的R类型.

PS:收集器其实就是一个可变的容器,我们可以随便操作

5.Collectors源码实现

Collectors静态工厂类,其中实现有两种情况:

  • 通过CollectorImpl来实现

  • 通过reducing方法来实现(本质也是CollectorImpl) ‘


1.Collectors.toList()方法,下面附源码,其实很简单

    public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    } 

这个是Collectors.toCollection()方法,必须传入参数 supplier(),用于指定中间返回类型和结果返回的集合类型,而Collectors.toList()在实现中已经指定,所以不需要传递参数

    public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }

    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());
    }

Mapping()在应用到多级汇聚的情况下最有用,例如:

Map<City, Set<String>> lastNamesByCity
     = people.stream().collect(groupingBy(Person::getCity, mapping(Person::getLastName, toSet())));


    public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                                Function<R,RR> finisher) {
        Set<Collector.Characteristics> characteristics = downstream.characteristics();
        if (characteristics.contains(Collector.Characteristics.IDENTITY_FINISH)) {
            if (characteristics.size() == 1)
                characteristics = Collectors.CH_NOID;
            else {
                characteristics = EnumSet.copyOf(characteristics);
                characteristics.remove(Collector.Characteristics.IDENTITY_FINISH);
                characteristics = Collections.unmodifiableSet(characteristics);
            }
        }
        return new CollectorImpl<>(downstream.supplier(),
                                   downstream.accumulator(),
                                   downstream.combiner(),
                                   downstream.finisher().andThen(finisher),
                                   characteristics);
    }

collectingAndThen() 通过适配器对上次的结果再做一次转换,转换为一个不可变的集合,源码中的示例:

     * <pre>{@code
     *     List<String> people
     *         = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
     * }</pre>

解读: 源码中通过if判断,其实本质上是把IDENTITY_FINISH去掉,通过上面的分析篇幅知道他的作用是不会调用 finisher()函数,会把结果进行强制转换,转换成一个最终的返回结果; 但是对于collectingAndThen()函数来说,必须要执行参数传递进来的这个finisher()函数.


    public static <T> Collector<T, ?, Integer>
    summingInt(ToIntFunction<? super T> mapper) {
        return new CollectorImpl<>(
                () -> new int[1],
                (a, t) -> { a[0] += mapper.applyAsInt(t); },
                (a, b) -> { a[0] += b[0]; return a; },
                a -> a[0], CH_NOID);
    }

summingInt() 函数的实现源码中先会创建一个supplier, 为啥是创建一个长度为1的() -> new int[1]的数组.,而不是直接创建一个数字?

因为数组是引用类型;数字是值类型,数字没法传递,所以不会能传递到后续的方法中进行累加,就只是一个固定的值;最重要的是搜集器必须是一个容器.


    public static <T> Collector<T, ?, Double>
    averagingInt(ToIntFunction<? super T> mapper) {
        return new CollectorImpl<>(
                () -> new long[2],
                (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; },
                (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
                a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID);
    }

averagingInt 这个函数的作用是求平均值,所以在创建收集器的时候会创建一个长度为2的数组容器,数组中第一个值就是累加所以值的总和,第二个值就是有几个数, 在源码中可以看出来,当在多线程的情况下,各自进行累加.


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

<T, K> T代表输入的元素类型

<T, K> K代表返回结果Map中的key的类型