数组克隆及对象的深、浅克隆(deep clone、shallow clone)

数组克隆及对象克隆的作用

在有的时候,我们需要分发出多个结构及内容相同,但各自间相互独立的实体,以用作业务需要(对于对象来说可能存在内部部分引用不独立的情况,此问题放在后面讨论)。比如说将数组 int[] body 或对象 Object body 拷贝出多份,分别命名为 body1,body2,body3,并且要求修改任何一个对象的时候其它对象均不会受到任何影响,此时就要用到克隆。


深克隆与浅克隆概念

先要说明的是,在java中数组被当作是对象来看待,同样也继承自Object类。


浅克隆(shallow clone)

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。


深克隆(deep clone)

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。


clone()方法

克隆数组有多种方法,而克隆对象则通过clone()方法来进行。同时,clone() 方法应当满足以下条件:

①对任何的对象body,都有body.clone() !=body//克隆对象与原对象不是同一个对象
②对任何的对象body,都有body.clone().getClass()= =body.getClass()//克隆对象与原对象的类型一样

③如果对象body的equals()方法定义恰当,那么body.clone().equals(body)应该成立。


克隆数组

克隆数组至少有以下四种思路:

1、使用Object类的clone()方法, 这种方法最简单,得到原数组的一个副本。灵活形也最差。效率最差,尤其是在数组元素很大或者复制对象数组时;

2、使用Systems的arraycopy()这种方法速度最快,并且灵活性也较好,可以指定原数组名称、以及元素的开始位置、复制的元素的个数,目标数组名称、目标数组的位置;

3、Arrays类的copyOf()方法与copyOfRange()方法可实现对数组的复制;

4、使用循环结构。这种方法最灵活。唯一不足的地方可能就是代码较多。

在此还得先普及几个知识:

1、java中没有“二维数组”、“n纬数组”这种说法,因此java数组的概念并不同等于C中的数组。

在java中所谓的二维数组,应当叫做“数组的数组”。例如:

int arr[][] = {{111,222,333}, {444,555,666}, {777,888,999}};

那么,arr[0][0]~arr[0][2]则成为“该数组中的第0组数组”。

2、java数组并不一定是矩形的或者是正体结构的,java允许不规则的数组结构体存在。例如,我可以这样声明数组:

int arr[][] = {{111,222,333}, {666}, {777,888,999,11,22}};
此时该数组的内存分配将会是这样的:

数组克隆及对象的深、浅克隆(deep clone、shallow clone)_第1张图片

如果我试图访问arr[1][1],必然会出现java.lang.ArrayIndexOutOfBoundsException异常。


使用Object.clone()进行克隆

我们通过一小段代码来展示:

	public static void main(String[] args) {
		int arr[] = {0,1,2,3,4,5};
		int copyarr[] = null;
		copyarr = arr.clone();
		
		copyarr[2] = 2048;

		System.out.println("arr == copyarr? " + (arr == copyarr));
		System.out.println("arr[2] is " + arr[2]);
		System.out.println("copyarr[2] is " + copyarr[2]);
	}
该段代码输出结果为:

arr == copyarr? false
arr[2] is 2
copyarr[2] is 2048

显然,这就是我们所预期的结果。arr通过clone()方法克隆出了新的数组,使得arr与copyarr并不引用同一个对象,因此对copyarr所做出的任何更改都将不会影响到arr本身。

与普通循环赋值创建新数组对比,该方法效率相对较高,因为Object.clone()调用的是本地方法:

protected native Object clone() throws CloneNotSupportedException;

使用System.arraycopy()进行克隆

我们在刚才的例子上做少许改动:

	public static void main(String[] args) {
		int arr[] = {0,1,2,3,4,5};
		int copyarr[] = new int[6];
		System.arraycopy(arr, 0, copyarr, 0, arr.length);
		copyarr[2] = 2048;

		System.out.println("arr == copyarr? " + (arr == copyarr));
		System.out.println("arr[2] is " + arr[2]);
		System.out.println("copyarr[2] is " + copyarr[2]);
	}
与Object.clone()不同,System.arraycopy()并不是返回一个新的数组,而是要求提供一个已经初始化的数组,并根据参数对新数组进行改动。查看调用方法,该方法亦是调用本地方法完成的:

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

使用Arrays.copyOf() / Arrays.copyOfRange()进行克隆

这两个方法无论使用的是哪个重载,最终都将是调用System.arraycopy()方法。Arrays类中的copy方法丰富及简化了拷贝的使用。

Arrays.copyOf()的其中一个重载方法:

    public static  T[] copyOf(U[] original, int newLength, Class newType) {
        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;
    }
Arrays.copyOfRange()的其中一个重载方法:

    public static  T[] copyOfRange(U[] original, int from, int to, Class newType) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
        return copy;
    }

使用循环结构进行克隆

接下来是数组拷贝中最具探讨性的内容。

现在,我们来尝试克隆二维数组,使用的方法是上面所提到的克隆方法。代码如下:

public static void main(String[] args) {
		int arr[][] = {{111,222,333},{666}};
		int copyarr[][] = new int[2][3];
		
		copyarr  = arr.clone();
		
		copyarr[0][0] = 9090;
		
		System.out.println("arr == copyarr? " + (arr == copyarr));
		System.out.println("arr[0] == b[0]? " + (arr[0] == copyarr[0]));
		System.out.println("arr[0][0] is " + arr[0][0]);
		System.out.println("b[0][0] is " + copyarr[0][0]);
	}
输出如下:

arr == copyarr? false
arr[0] == b[0]? true
arr[0][0] is 9090
b[0][0] is 9090

奇怪了,这似乎不是我们想要的结果!

我们发现,数组arr和copyarr并不是同一个对象,但是他们的子数组却指向同一个对象,因此导致了改变其中一个数组的值,导致另一个数组随之改变。

别忘了,在java中并没有二维数组的概念。每一个数组都是一个对象,而数组的数组,则是这个数组里面的一个对象而已。因此,如果你在类似上述例子的数组使用克隆,从而导致了只克隆了第一层而没有克隆内部数组,这种现象我们称之为浅克隆。请查看浅克隆的定义,在这里我们正好符合。

如果想要做到整个数组完全被克隆,则需要对数组中的数组进行克隆。同样,如果存在数组的数组的数组,那么则要继续深入内部进行克隆。这种情况下我们可以使用循环进行克隆,代码如下:

	public static void main(String[] args) {
		int arr[][] = {{111,222,333},{666}};
		int copyarr[][] = new int[2][3];

		for (int i = 0; i < arr.length; i++) {
			copyarr[i] = arr[i].clone();
		}
		
		copyarr[0][0] = 9090;
		
		System.out.println("arr == copyarr? " + (arr == copyarr));
		System.out.println("arr[0] == copyarr[0]? " + (arr[0] == copyarr[0]));
		System.out.println("arr[0][0] is " + arr[0][0]);
		System.out.println("copyarr[0][0] is " + copyarr[0][0]);
	}
输出如下:

arr == copyarr? false
arr[0] == copyarr[0]? false
arr[0][0] is 111
copyarr[0][0] is 9090

数组arr与数组copyarr的全部引用均不指向同一个地址,这样我们便完成了深克隆。当然,如果你的数组结构极为复杂,则可以使用迭代方式进行循环,在此不进行演示。

以上部分内容转载或参考来源如下:

http://www.cppblog.com/baby-fly/archive/2010/11/16/133763.html?opt=admin

http://greemranqq.iteye.com/blog/1750028

在此表示感谢。
转载请注明来源,版权归原作者所有,未经同意严禁用于任何商业用途。
微博:http://weibo.com/theworldsong
邮箱:[email protected]


你可能感兴趣的:(Java开发)