前言
由于最近在准备面试,因此决定把JDK常用的集合源码进行分析,从而深入理解Java集合。本文章不会一行一行的去解析代码,而是尽可能地根据源码,结合自己的理解进行分析,形成有思想程度的总结。
Iterable源码分析
从以上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图,如下:
通过UML图,我们可以看出Iterator提供了对集合迭代遍历的功能。
Iterator对remove以及forEachRemaining方法提供了默认的实现,其中Iterator默认是支持remove,剩下的forEachRemaining的功能与Iterable的forEach异曲同工。
Collection源码分析
通过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的UML图,可以看出Set拓展了Collection接口,也具备Iterable接口的功能。我们再看Set集合的函数,
大部分的函数命名与Collection的一致。
Map源码分析
通过Map的UML图,可以看出Map作为一个最原始的接口,不像Set接口那样,去拓展Collection接口,我认为,Map提供了映射关系信息的存、取能力,与集合Collection存储的单元素是不一样的概念的,因此这里是不需要拓展Collection接口的。
其中Map接口内,嵌入了Entry接口,Entry提供的功能,如图:
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等这些函数,都是利用了模板方法设计模式来解耦处理。