小问题4 | Arrays.copyOf(elementData, size, a.getClass())是如何实现的?
上一个问题(小问题2 | 为什么对一个List调用toArray(new String[0])就可以把List转换成对应的数组?)中我们看到:
List中的toArray方法,无论是public Object[] toArray(),还是重载的public T[] toArray(T[] a),在源码中都调用了Arrays.copyOf方法。
Arrays.copyOf(elementData, size, a.getClass())是如何实现的?
我们直接来看Arrays.copyOf方法的源码:
// 以int类型数组为例,其他类型类似
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
@SuppressWarnings("unchecked")
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;
}
在源码中,我们看到了一个清晰的调用结构,无论是从第一个方法进入还是从第二个方法进入,最终都是调用
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) 这个更具普适性的方法。
下面我们对该方法进行拆解:
首先,代码对传入的数组是否是Object类型数组进行判断:
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
如果原数组是Object类型数组,则直接创建一个给定的newLength长度的新Object数组(此处创建新数组的目的是,为了在下一步的操作中接收复制结果)
如果原数组不是Object类型数组,则调用 (T[]) Array.newInstance(newType.getComponentType(), newLength) 这个方法,新建一个数组,数组类型为newType中元素的类型(默认返回Object类型,可强制转换为正确类型),长度为给定的newLength。
而Array.newInstance方法内部,直接调用Array.newArray,newArray为本地方法,由虚拟机实现。
接着,代码调用System.arraycopy方法:
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
在此简单说明一下该方法,根据java源码注释的翻译,对该方法参数的说明如下:
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
@param src 原数组
* @param srcPos 原数组开始位置
* @param dest 目标数组
* @param destPos 目标数组开始位置.
* @param length 复制的长度
* @exception IndexOutOfBoundsException 数组越界
* @exception ArrayStoreException 类型不匹配
* @exception NullPointerException 原数组或目标数组为Null
由注释我们可知,此处通过 System.arraycopy方法实现了原数组到新数组的复制,而这里的System.arraycopy方法是一个静态的native方法,可以使用这个方法来实现数组之间的复制,该方法实现了对数组的浅拷贝。
什么是浅拷贝?
Java数组的复制操作可以分为深拷贝和浅拷贝,简单来说**,深拷贝,指拷贝对象的值和对象的内容**;而浅拷贝,只是对对象引用内容的复制。
对于一维数组来说,这种复制属性值传递,修改副本不会影响原来的值。
对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。
简而言之,我个人的理解,这部分意味着——System.arraycopy只能复制一个维度的数据,二维及以上则是浅复制,只有在一维的是深复制。
是的,这里有两个新问题,当我们不想只是拷贝引用地址的时候——
小问题6 | Java中如何实现深拷贝?实现深拷贝的三种方法
另一个问题是,有朋友可能对刚刚提到的Native方法比较陌生——
小问题5 | 什么是native方法?附JNI介绍
当然,有兴趣的话,我们更能够研究一下如何在我们的代码中使用。
我们带着这些问题继续看。
————————————————————
本专栏是【小问题】系列,旨在每篇解决一个小问题,并秉持着刨根问底的态度解决这个问题可能带出的一系列问题。