.

.

.

.

.

目录

(一)一起学 Java Collections Framework 源码之 概述

(二)一起学 Java Collections Framework 源码之 AbstractCollection

java.util.AbstractCollection 类提供了 java.util.Collection 接口的骨干实现,也是 Java 集合框架(JCF, Java Collections Framework)中列表(List/Set)族相对较为顶层的实现类,这一点大家通过上一篇博文的 图1 就可以看出来了。此类是抽象类,因此仅仅实现了 Collection 接口中一些最基本的方法。由于此类没有实现 List 接口,所以它的子类未必都是有序的,因此它可以作为 List(有序)实现类和 Set(无序)实现类的共同祖先类。

JCF 为容器的基本实现提供了多个 Abstract 类,因此掌握了这些类之后,我们自己基于这些 Abstract 类再实现新的容器时只要实现那些必要的方法即可。

下面我们对 AbstractCollection 类中一些常见的方法实现逐个进行分析。

1.iterator() 方法

public abstract Iterator<E> iterator();

此方法返回一个迭代器,该迭代器用于遍历这个列表中的每一个元素。由于 java.util.Iterator 也是一个接口,所以不同的数据结构实现(也就是不同的子类)可以根据自己的需要来定义不同的迭代方式,所以此方法被定义为一个抽象方法,没有对它进行实现。

2.isEmpty() 方法

 public boolean isEmpty() {
return size() == 0;
}

这个很好理解,用来判断当前集合是不是空的,不做过多的解释。

3.contains(Object o) 方法

 public boolean contains(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}

此方法用来判断当前集合中是否包含一个与传入的参数 o 相同的元素。

想要匹配某个元素就必须要遍历集合了,于是先获得迭代器,然后根据参数 o 是否为 null 分别进行不同的处理。

这里需要注意的是,在参数 o 不为 null 时采用的是 equals 方法来对比的。如果我们要使用特定的规则来判断这个对象是否存在于某个集合中,通过重写该对象的 equals() 方法即可实现。

4.toArray() 方法

 public Object[] toArray() {
// Estimate size of array; be prepared to see more or fewer elements
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) // fewer elements than expected
return Arrays.copyOf(r, i);
r[i] = it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
} private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; @SuppressWarnings("unchecked")
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
int i = r.length;
while (it.hasNext()) {
int cap = r.length;
if (i == cap) {
int newCap = cap + (cap >> 1) + 1;
// overflow-conscious code
if (newCap - MAX_ARRAY_SIZE > 0)
newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T)it.next();
}
// trim if overallocated
return (i == r.length) ? r : Arrays.copyOf(r, i);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError
("Required array size too large");
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

toArray() 方法调用了两个私有方法,虽然看起来比较长,但解读起来并不复杂。

这个方法用于将集合中所有元素转换为数组。

首先创建一个与集合长度相同的数组 r,并通过迭代器将集合中的每一个元素按照迭代的顺序依次保存到数组 r 中。

但由于获取集合长度的方法 size() 是由子类来实现的,所以无法避免两种情况发生:

1) size() 返回的长度比真正集合中元素的数量多;

2) size() 返回的长度比真正集合中元素的数量少。

数组 r 的长度又是固定的,怎么办?

第一种情况,通过 java.util.Arrays.copyOf(Object[] original, int newLength) 方法根据真正的元素数量再创建一个新的数组,然后将 r 内现有的元素拷贝进去再返回即可。

第二种情况稍微麻烦一点,因为此时并不知道集合中还有多少个元素没有被迭代出来,因此是不能直接确定新分配的数组的长度的。

这时候两个私有函数就派上用场了:finishToArray(T[] r, Iterator<?> it) 方法会继续迭代这个集合,在遍历的过程中会通过 java.util.Arrays.copyOf(Object[] original, int newLength) 方法分配一个新的数组并赋给变量 r,长度是原先 r 数组的1.5倍左右(newCap = cap + (cap >> 1) + 1),然后继续将集合中后面迭代出来的元素按顺序保存到数组 r 中。直到 r 再次满了,那么再重复上面的步骤继续分配空间。

最后一行 return (i == r.length) ? r : Arrays.copyOf(r, i); 的意思是,若按照前面每次分配 1.5 倍新空间的增长方式,若最终迭代完毕后整好填满了数组 r,那么直接返回 r 就可以了;若新分配的空间并没有完全被用完集合就遍历结束了,那么再重新分配一个与真实元素数量相同长度的数组来保存 r 的数据,并返回给调用者。关于这一点,大家看一下 java.util.Arrays.copyOf(Object[] original, int newLength) 的源码就明白了。

私有函数 hugeCapacity(int minCapacity) 的作用是检查分配的元素数量有没有超过上限(MAX_ARRAY_SIZE)。

5.toArray(T[] a) 方法

 @SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
int size = size();
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator(); for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) { // fewer elements than expected
if (a == r) {
r[i] = null; // null-terminate
} else if (a.length < i) {
return Arrays.copyOf(r, i);
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
r[i] = (T)it.next();
}
// more elements than expected
return it.hasNext() ? finishToArray(r, it) : r;
}

与 toArray() 方法相同,也是用于将集合转换为数组。

这里需要注意的两点:

1) 若参数 a 的长度小于集合的长度,此函数会在内部重新分配数组用于返回值,所以调用者的入参不会被改变。所以建议调用者使用其返回值而不是入参作为后续处理的数据源。

2) 若参数 a 的长度大于集合的长度,则参数 a 比此集合多出来的部分会被赋为 null。

大家不妨运行下面的代码,通过修改 len 来控制数组 ary0 的长度进行测试,观察运行结果。

 public static void main(String[] args) {
AbstractCollection<String> c = new ArrayList<String>();
c.add("a");
c.add("b");
c.add("c");
c.add("d");
int len = 5;
String [] ary0 = new String [len];
ary0[4] = "eee";
String [] ary1 = c.toArray(ary0);
print(ary0);
System.out.println("ary1.length = " + ary1.length);
print(ary1);
} private static void print(String [] ary) {
for (int i = 0; i < ary.length; i++) {
System.out.println("ary[" + i + "]" + ary[i]);
}
}

6.remove(Object o) 方法

 public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}

此方法用于从集合中删除一个与参数 o 相同的元素。

这个方法的实现与 contains(Object o) 方法十分相似,只不过是多调用了一下迭代器中的 remove() 方法而已。

同样需要注意这里比较对象使用的是 equals() 方法,因此可以通过重写对象 equals() 方法的方式来自定义删除规则。

7.containsAll(Collection<?> c) 方法

 public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}

此方法用来测试当前集合是否包含集合 c 中的每一个元素。

实现方式很简单,迭代集合 c,测试其每一个元素是否被包含在当前的集合中,若有任何一个元素不包含在当前集合中,则返回 false。

8.addAll(Collection<? extends E> c) 方法

 public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}

此方法将集合 c 中的每一个元素依次添加到本集合中。

但请注意,只要集合 c 中的任何一个元素成功添加到了当前集合中,即使其它元素全部添加失败了,此方法也会返回 true。所以官方 API 文档对于返回值给出的解释是:如果此 collection 由于调用而发生更改,则返回 true。

9.removeAll(Collection<?> c) 方法

 public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}

此方法用于从当前的集合中移除所有与 c 集合相同成员的成员。此方法的作用并不是无条件清空本集合,若需如此做,要调用 clear() 方法。

实现非常简单,迭代遍历,只要包含相同的成员就 remove() 掉。

这里的 Objects.requireNonNull(c); 用于判断入参 c 是否为 null,若为 null 就跑出空指针异常。java.util.Objects 类是从 JDK 1.7 版本开始提供的。

10.retainAll(Collection<?> c) 方法

 public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}

此方法与 removeAll(Collection<?> c) 方法恰恰相反,仅保留当前集合中与集合 c 中相同的元素,也就是说将本集合中与集合 c 中的不同的元素都删掉。

实现方式与 removeAll(Collection<?> c) 方法完全相同,只有测试包含的条件是相反的。

11.clear() 方法

 public void clear() {
Iterator<E> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}

无条件清空当前集合。迭代删除每一个元素即可。

(二)一起学 Java Collections Framework 源码之 AbstractCollection的更多相关文章

  1. (一)一起学 Java Collections Framework 源码之 概述

    . . . . . 目录 (一)一起学 Java Collections Framework 源码之 概述 JDK 中很多类 LZ 已经使用了无数次,但认认真真从源码级研究过其原理的还只占少数,虽然从 ...

  2. Java学习-039-源码 jar 包的二次开发扩展实例(源码修改)

    最近在使用已有的一些 jar 包时,发现有些 jar 包中的一些方法无法满足自己的一些需求,例如返回固定的格式,字符串处理等等,因而需要对原有 jar 文件中对应的 class 文件进行二次开发扩展, ...

  3. Java集合框架源码(二)——hashSet

    注:本人的源码基于JDK1.8.0,JDK的版本可以在命令行模式下通过java -version命令查看. 在前面的博文(Java集合框架源码(一)——hashMap)中我们详细讲了HashMap的原 ...

  4. 如何读懂Framework源码?如何从应用深入到Framework?

    如何读懂Framework源码? 首先,我也是一个应用层开发者,我想大部分有"如何读懂Framework源码?"这个疑问的,应该大都是应用层开发. 那对于我们来讲,读源码最大的问题 ...

  5. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  6. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  7. 【java集合框架源码剖析系列】java源码剖析之LinkedList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...

  8. Java集合框架源码分析(2)LinkedList

    链表(LinkedList) 数组(array)和数组列表(ArrayList)都有一个重大的缺陷: 从数组的中间位置删除一个元素要付出很大的代价,因为数组中在被删除元素之后的所有元素都要向数组的前端 ...

  9. Java Collections Framework概览

    本文github地址 概览 容器,就是可以容纳其他Java对象的对象.Java Collections Framework(JCF)为Java开发者提供了通用的容器,其始于JDK 1.2,优点是: 降 ...

随机推荐

  1. vs2017 .net core WebApp 去掉ApplicationInsights

    vs2017新建的 .net core WebApp都内置了这个遥测中间件进去,嗯,用AZURE的话是不错能无缝支持.但不用AZURE就没什么用了. 为了不占地方和提高一点点初始启动的速度,对新建的项 ...

  2. C语言学习心得

    最近学习了C语言,打脑壳,很多东西不会用,没有概念,单点知识都懂,组合起来就不知道怎么弄了.慢慢来吧

  3. Java面试11|Maven与Git

    git的命令一定要掌握,如果学习可以参考:廖雪峰的官方网站 1.Maven 生命周期及Maven多项目聚合与继承 Maven的生命周期分如下的9个阶段. (1)clean 清理自动生成的文件,也就是t ...

  4. 深入理解ajax系列第四篇——FormData

    前面的话 现代Web应用中频繁使用的一项功能就是表单数据的序列化,XMLHttpRequest 2级为此定义了FormData类型.FormData为序列化表单以及创建与表单格式相同的数据提供了便利. ...

  5. KoaHub平台基于Node.js开发的Koa的连接MongoDB插件代码详情

    koa-mongo MongoDB middleware for koa, support connection pool. koa-mongo koa-mongo is a mongodb midd ...

  6. MD5加密 32位

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; ...

  7. JavaWeb之Servlet总结

    今天上班居然迟到了,昨天失眠了,看完吐槽大会实在不知道做些什么,刚好朋友给我发了两个JavaWeb的练习项目,自己就又研究了下,三四点才睡,可能周日白天睡的太多了,早上醒来已经九点多了,立马刷牙洗脸头 ...

  8. 《C++之那些年踩过的坑(二)》

    C++之那些年踩过的坑(二) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. 今天讲一个小点,虽然小,但如果没有 ...

  9. HTML5初步了解

        一.使用HTML5的十大原因 你难道还没有考虑使用HTML5? 当然我猜想你可能有自己的原因:它现在还没有被广泛的支持,在IE中不好使,或者你就是喜欢写比较严格的XHTML代码.HTML5是w ...

  10. struct和typedef struct在c++中的用法

    #include<iostream> using namespace std; struct test{ int a; }test; //定义了结构体类型test,声明变量时候直接test ...