(二)一起学 Java Collections Framework 源码之 AbstractCollection
.
.
.
.
.
目录
(一)一起学 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的更多相关文章
- (一)一起学 Java Collections Framework 源码之 概述
. . . . . 目录 (一)一起学 Java Collections Framework 源码之 概述 JDK 中很多类 LZ 已经使用了无数次,但认认真真从源码级研究过其原理的还只占少数,虽然从 ...
- Java学习-039-源码 jar 包的二次开发扩展实例(源码修改)
最近在使用已有的一些 jar 包时,发现有些 jar 包中的一些方法无法满足自己的一些需求,例如返回固定的格式,字符串处理等等,因而需要对原有 jar 文件中对应的 class 文件进行二次开发扩展, ...
- Java集合框架源码(二)——hashSet
注:本人的源码基于JDK1.8.0,JDK的版本可以在命令行模式下通过java -version命令查看. 在前面的博文(Java集合框架源码(一)——hashMap)中我们详细讲了HashMap的原 ...
- 如何读懂Framework源码?如何从应用深入到Framework?
如何读懂Framework源码? 首先,我也是一个应用层开发者,我想大部分有"如何读懂Framework源码?"这个疑问的,应该大都是应用层开发. 那对于我们来讲,读源码最大的问题 ...
- 【java集合框架源码剖析系列】java源码剖析之TreeSet
本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...
- 【java集合框架源码剖析系列】java源码剖析之HashSet
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...
- 【java集合框架源码剖析系列】java源码剖析之LinkedList
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...
- Java集合框架源码分析(2)LinkedList
链表(LinkedList) 数组(array)和数组列表(ArrayList)都有一个重大的缺陷: 从数组的中间位置删除一个元素要付出很大的代价,因为数组中在被删除元素之后的所有元素都要向数组的前端 ...
- Java Collections Framework概览
本文github地址 概览 容器,就是可以容纳其他Java对象的对象.Java Collections Framework(JCF)为Java开发者提供了通用的容器,其始于JDK 1.2,优点是: 降 ...
随机推荐
- Jquery之JSON的用法
今天讲了Jquery里面JSON的用法,下面是今天讲课给的例子: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" &quo ...
- 算法模板——splay区间反转 1
实现的功能:将序列区间反转,并维护 详见BZOJ3223 var i,j,k,l,m,n,head,a1,a2:longint; s1:ansistring; a,b,c,d,fat,lef,rig: ...
- Omi应用md2site-0.5.0发布-支持动态markdown拉取解析
写在前面 Md2site是基于Omi的一款Markdown转网站工具,使用简单,生成的文件轻巧,功能强大. 官网:http://alloyteam.github.io/omi/md2site/ Git ...
- 高并发解决方案之Actor——第一节
还在为状态的并发控制而痛苦吗? 还在因为数据库瓶颈而痛苦吗? 还在因为缓存的实时性控制而痛苦吗? 还在为了想分布式,但又不知道怎么下手而痛苦吗? Actor欢迎你!!! 一.什么是 ...
- 玩转Node.js单元测试
代码部署之前,进行一定的单元测试是十分必要的,这样能够有效并且持续保证代码质量.而实践表明,高质量的单元测试还可以帮助我们完善自己的代码.这篇博客将通过一些简单的测试案例,介绍几款Node.js测试模 ...
- 苹果ATS特性服务器证书配置指南
配置指南: 需要配置符合PFS规范的加密套餐,目前推荐配置: ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!AD ...
- 如何下载github项目中的部分文件(文件夹)
https://minhaskamal.github.io/DownGit/#/home 将你要下载的链接放进去即可.
- java里的日期时间
为了更好理解java的日期时间类,在这里我们先介绍一下关于历法.标准时间的一些概念. 历法有很多种,我们大中华上下五千年,自然也有自己的历法,生活中我们通常把自己传统的历法叫做农历,也有人叫它阴历或夏 ...
- JS——函数、事件
1.函数字符串函数 var s=new string(); var ss="hello world"; var sss=""HELLO, WORLD" ...
- 【转】关于swf安全沙箱冲突:不能被本地访问
错误提示:SWF 文件不能被本地访问 不能访问本地只有仅限于文件系统的 SWF 文件和可信的本地 SWF 文件可以访问本地资源 错误信息:SecurityError:Error #2148: SWF ...