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

时间:2022-09-26 00:25:42

.

.

.

.

.

目录

(一)一起学 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&quest;

    如何读懂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. 黑马程序员-hashtable

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列 ...

  2. iptabels 的一些配置

    iptables -L -n iptables -F   iptables -P INPUT DROP iptables -P OUTPUT ACCEPT iptables -P FORWARD DR ...

  3. datatables 学习笔记1 基础篇

    本文共3部分:基本使用|遇到的问题|属性表 1.DataTables的默认配置 $(document).ready(function() { $('#example').dataTable(); } ...

  4. 《程序员修炼之道:从小工到专家》【PDF】下载

    <程序员修炼之道:从小工到专家>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196340 内容简介 <程序员修炼之道> ...

  5. Effective Java 第三版——9&period; 使用try-with-resources语句替代try-finally语句

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. Kail Linux的安装方法

    众所周知,kail 是一个基于Debian的Linux发行版,它的目标就是为了在一个实用的工具包里尽可能多的包含渗透和审计工具 kail就实现了这个目标,里面包含有很多关于安全测试的开源工具,如果现在 ...

  7. phpinfo

    phpinfo是一个运行指令,为显示php服务器的配置信息.    phpinfo函数是PHP最为常用的配置输出函数.phpinfo函数能够输出服务器PHP当前状态的大量信息,其中包含了PHP的编译选 ...

  8. 关于Https

    http://blog.csdn.net/wfdtxz/article/details/8678982 https://www.tuicool.com/articles/feYfE3I https:/ ...

  9. General PE format layout

  10. CSS的块级元素和内联元素,以及float

    说明:之前有一点搞错了,就是float其实是浮动起来,其它元素会位于它的底层. 最近在系统地学习HTML5,感觉补上了好多缺失的知识. 例如: 锚点定位其实可以通过 id 来实现: CSS 使用 !i ...