前言
(PS:上周由于准备自考,更新延误了一些,见谅!)
AbstractCollection
类是Collection
接口的一个实现类,它只是为了给我们提供了一些最基础的接口的实现,在实际的应用中,我们并不会去使用这个类来封装一些信息,接下来我们首先去看一下这个类的继承关系图。
结构图
由此可以看出AbStractCollection
只是实现了Collection
接口,并没有实现或者继承其他的类或者接口。接下来,我们来看一下这个类的源码,看看会有什么收获。
源码
package java.util;
//TAG 1
public abstract class AbstractCollection<E> implements Collection<E> {
protected AbstractCollection() {
}
public abstract Iterator<E> iterator();
public abstract int size();
//TAG 2
public boolean isEmpty() {
return size() == 0;
}
//TAG 3
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;
}
//TAG 3
public Object[] toArray() {
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext())
return Arrays.copyOf(r, i);
r[i] = it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
//TAG 3
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
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()) {
if (a == r) {
r[i] = null;
} 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();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
//TAG 4
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//TAG 5
@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;
if (newCap - MAX_ARRAY_SIZE > 0)
newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T)it.next();
}
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
//TAG 6
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;
}
//TAG 7
public boolean add(E e) {
throw new UnsupportedOperationException();
}
//TAG 3
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;
}
//TAG 3
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
//TAG 3
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
//TAG 3
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;
}
//TAG 3
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;
}
//TAG 3
public void clear() {
Iterator<E> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
//TAG 3
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
}
TAG 1 :
从关键字abstract
可以看出这个类是一个抽象类,这个类只是实现了一部分的接口,还遗留了一部分接口需要我们在子类中去实现。
TAG 2 :
isEmpty()
方法通过size()
接口是否等于0来判断,而size()
接口的实现交给了更为具体的集合类去实现。
TAG 3 :
contains()
,remove()
,containsAll()
,addAll()
,removeAll()
,retainAll()
,clear()
,toString()
这些方法我们通过阅读源码可以知道,都是通过迭代器来实现的,其中有一些方法使用了增强for循环,但其实编译器会将增强for循环编译为使用迭代器的遍历操作。
TAG 4 :
数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8 byte。
TAG 5 :
finishToArray(T[] r, Iterator<?> it)
方法用于数组扩容,当数组索引指向最后一个元素+1时,对数组进行扩容:即创建一个大小为(cap + cap/2 +1)的数组,然后将原数组的内容复制到新数组中。扩容前需要先判断是否数组长度是否溢出。这里的迭代器是从上层的方法(toArray(T[] t)
)传过来的,并且这个迭代器已执行了一部分,而不是从头开始迭代的
TAG 6 :
hugeCapacity(int minCapacity)
方法用来判断该容器是否已经超过了该集合类默认的最大值即(Integer.MAX_VALUE -8
),一般我们用到这个方法的时候比较少,后面我们会在ArrayList
类的学习中,看到ArrayList
动态扩容用到了这个方法。
TAG 7 :
这里的add(E)
方法默认抛出了一个异常,为什么这样去定义呢?而不是直接定义为抽象方法?
如果我们想修改一个不可变的集合时,抛出 UnsupportedOperationException
是正常的行为,比如当你用 Collections.unmodifiableXXX()
方法对某个集合进行处理后,再调用这个集合的修改方法(add
,remove
,set…
),都会报这个错。因此 AbstractCollection.add(E)
抛出这个错误是准从标准。
而之所以没有定义为抽象方法,是因为可能有很多地方用不到这个方法,用不到还必须实现,这岂不是让人很困惑么。(PS:参考自拭心)
toArray详解
在这个类中,我们需要详细了解的方法是toArray
,正如其名,它可以把一个集合转换为数组,可以看到toArray
方法分为了两种参数的方法:
/**
* 分配了一个等大空间的数组,然后依次对数组元素进行赋值
*/
public Object[] toArray() {
//新建等大的数组
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
//判断是否遍历结束,以防多线程操作的时候集合变得更小
if (! it.hasNext())
return Arrays.copyOf(r, i);
r[i] = it.next();
}
//判断是否遍历未结束,以防多线程操作的时候集合变得更大,进行扩容
return it.hasNext() ? finishToArray(r, it) : r;
}
/**
* 泛型方法的`toArray(T[] a)`方法在处理里,会先判断参数数组的大小,
* 如果空间足够就使用参数作为元素存储,如果不够则新分配一个。
* 在循环中的判断也是一样,如果参数a能够存储则返回a,如果不能再新分配。
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
//当数组a的长度大于等于a,直接将a赋予给r,否则使用反射API获取一个长度为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()) {
//如果 a == r,将r的每项值赋空,并将a返回
if (a == r) {
r[i] = null;
} else if (a.length < i) {
//如果a的长度小于r,直接调用Arrays.copyOf进行复制获取一个新的数组
return Arrays.copyOf(r, i);
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
//如果遍历结束,将迭代器获取的值赋给r
r[i] = (T)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;
if (newCap - MAX_ARRAY_SIZE > 0)
newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T)it.next();
}
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;
}
为了帮助了解,我把Arrays.copyOf(r.i)
的源码也贴出来:
//参数original代表你传入的需要复制的泛型数组,newLength复制得到数组的大小
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
我们可以观察到其中调用了System.arraycopy
方法,为了保持刨根问底的态度,我们又去翻看了这个方法的源码:
//src数组里从索引为srcPos的元素开始, 复制到数组dest里的索引为destPos的位置, 复制的元素个数为length个.
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length);
可以看到这个方式是由关键字native
修饰的方法,那么native
修饰的方法有什么含义呢?
native
关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
JNI是Java本机接口(Java Native Interface),是一个本机编程接口,它是Java软件开发工具箱(java Software Development Kit,SDK)的一部分。JNI允许Java代码使用以其他语言编写的代码和代码库。Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。
然后我们来分析toArray()
中需要注意的点,通过原源码中的英文注解,toArray
得到的数组跟原collection
没有任何关系,我们可以对数组的每个引用值做修改,而不会影响到原collection.这个看起来好像是多余说明的,但是考虑到ArrayList其实就是基于数组实现的,那这个限制保证了即使是将ArrayList转化为数组,那也应该是分配一个新数组,而不是返回原来的数组。
如果我们在单线程操作的情况下,collection集合大小不变,正常应该是执行到 return it.hasNext() ? finishToArray(r, it) : r;
这条语句结束,但考虑到在复制的过程中,collection
的集合可能会有变化,可能是变大也可能是变小,所以方法增加了对这种情况的处理,这就是为什么每次循环都要判断是collection是否遍历完,以及最后再判断collection
是否变得更长,如果是的话,还需要重新再为array分配空间。
通常情况下,我们不会执行到hugeCapacity
,但作为一个框架来说,这体现了设计时的严谨。
下节预告
下节课,我们会刨根问底的去了解List
的使用。
公众号
扫码或微信搜索 Vi的技术博客,关注公众号,不定期送书活动各种福利~