Collection、Iterable、Set&Map接口源码分析

前言

由于最近在准备面试,因此决定把JDK常用的集合源码进行分析,从而深入理解Java集合。本文章不会一行一行的去解析代码,而是尽可能地根据源码,结合自己的理解进行分析,形成有思想程度的总结。

Iterable源码分析

Iterable.png

从以上UML图,可以看出Iterable接口只定义了三个函数,关于以上的spliterator()函数后续若涉及到Spliterator类再进行分析。以下主要是对forEach以及iterator进行分析。

forEach

我们看下源码:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
复制代码

通过源码可以知道,Iterable提供了默认的实现,入参选用了Consumer函数式接口,Consumer接口的主要功能是通过accept来消费一个元素,由于它是函数式接口,因此我们可以自定义消费处理方式,通过forEach的入参传入即可。

iterator

此方法用于获取集合的迭代器,看下iterator的UML图,如下:

Iterator.png

通过UML图,我们可以看出Iterator提供了对集合迭代遍历的功能。
Iterator对remove以及forEachRemaining方法提供了默认的实现,其中Iterator默认是支持remove,剩下的forEachRemaining的功能与Iterable的forEach异曲同工。

Collection源码分析

Collection.png

通过Collection的UML图,我们可以看出Collection拓展了Iterable接口,增加了对集合进行增删改查的相关功能。Collection接口也对部分函数提供了默认的实现,以下具体分析。

removeIf

removeIf利用了函数式接口Predicate提供的过滤功能来判断是否需要删除某个元素,源码如下:

    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
复制代码

入参为Predicate, 可以由用户自定义需要删除元素的规则,在源码中通过调用Predicate.test方法就可以判断当前元素是否符合用户自定义的删除规则,若符合,就利用迭代器接口的remove操作。

当然,这里利用迭代器删除元素是否正确的,因为迭代器删除集合元素时,若集合发生了变更,迭代器会产生快速失败异常。

Set源码分析

Set.png

通过Set的UML图,可以看出Set拓展了Collection接口,也具备Iterable接口的功能。我们再看Set集合的函数,
大部分的函数命名与Collection的一致。

Map源码分析

Map.png

通过Map的UML图,可以看出Map作为一个最原始的接口,不像Set接口那样,去拓展Collection接口,我认为,Map提供了映射关系信息的存、取能力,与集合Collection存储的单元素是不一样的概念的,因此这里是不需要拓展Collection接口的。

其中Map接口内,嵌入了Entry接口,Entry提供的功能,如图:

image.png

Map接口可以通过entrySet()方法来获取Entry的集合,源码如下:

 Set<Map.Entry<K, V>> entrySet();
复制代码

在获取Entry集合后,我们可以遍历此集合,再通过Entry提供的getKey(), getValue()方法获取Map存储元素的信息。

getOrDefault

这个函数再日常开发中也是经常使用的,源码如下:

    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
    }
复制代码

getOrDefault提供的能力时,当根据入参key不存在与Map中,或者根据key查询到value,但value为null,那么这个时候使用的是入参defaultValue,否则使用的是key对应的value。

forEach

源码如下:

    default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }
复制代码

再Iterable中也存在forEach, 他们之间处理的逻辑基本一样,唯一不一样的是,Map中的forEach的入参是BiConsumer,Iterable中的入参是Consumer,我认为他们之间本质是一样的,也是消费一个元素,唯一不一样的是BiConsumer支持两个入参。

replaceAll

源码如下:

    default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }

            // ise thrown from function is not a cme.
            v = function.apply(k, v);

            try {
                entry.setValue(v);
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
        }
    }
复制代码

全量替换Map中的value, 通过遍历EntrySet来获取每个元素的key以及value, 再通过函数式BiFunction中的apply方法来对value进行转换并得到结果,将结果通过entry.setValue方法进行更新,以此达到全局替换的效果。

computeIfPresent

源码如下:

    default V computeIfPresent(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        V oldValue;
        if ((oldValue = get(key)) != null) {
            V newValue = remappingFunction.apply(key, oldValue);
            if (newValue != null) {
                put(key, newValue);
                return newValue;
            } else {
                remove(key);
                return null;
            }
        } else {
            return null;
        }
    }

复制代码

这类函数,核心还是利用了BiFunction,用户自定义BiFunction的apply方法,可以将旧值转换为新值,当新值不为空时,则进行更新处理,若新值为空,那么久从Map中删除指定的key。

总结

通过以上的学习,我们知道Map的本质是映射,Collection与Set都是提供对接集合元素的增删改查等功能。同时,也学习到了,利用函数式接口,如Consumer、Function、Predicate作为入参,可以由用户自定义处理规则,这样是比较灵活地。最后,我认为,在Map中的compute、Iterable.forEach等这些函数,都是利用了模板方法设计模式来解耦处理。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享