转载

深入浅出,如何更彻底地理解Java数组的clone方法

说在前面

在进入理解clone前,我们需要对“基本数据类型”和“引用数据类型”的存储模式有一个清晰的认识。

基本数据类型,变量的内容保存的是实际的值;引用数据类型,变量的内容保存的是一个地址,该地址的指向才是实际的值。

int baseData = 5;     // 基本数据类型,baseData对应的内存保存的是具体的值:5
    System.out.println(baseData); // 直接打印,返回:5
    
    HandClass handData = new HandClass(); //引用数据类型,handData对应的内存保存的是一个16进制的内存地址,该地址才是保存值的地方
    System.out.println(handData); //直接打印,返回内存地址:HandClass@3c6f579

需要注意的是,不管是基本数据类型,还是引用数据类型,赋值(=)操作都是将变量自身内容赋值给另一个变量。 唯一不同的是,我们常用操作中,基本数据类型是针对自身内容(值)进行的;而引用数据类型则是针对自身内容(地址)的指向进行的。

int data1 = 1;
    int data2 = data1; // 将data1的内容(1)赋值给data2
    System.out.println(data1); // 返回:1
    System.out.println(data2); // 返回:1

    data1 = 2; // 即使修改data1的内容,data2不受影响

    System.out.println(data1); // 返回:2
    System.out.println(data2); // 返回:1

    System.out.println("--------------------");


    HandClass handData1 = new HandClass();
    handData1.ele = 1;
    HandClass handData2 = handData1; // 将handData1的内容(实际内存地址)赋值给handData2
    System.out.println(handData1.ele); // 返回:1
    System.out.println(handData2.ele); // 返回:1

    // 直接将handData1和handData2打印出来,返回相同的内容(地址)
    System.out.println(handData1); // 返回:HandClass@66edc3a2
    System.out.println(handData2); // 返回:HandClass@66edc3a2

    handData1.ele = 2; // 修改handData1.ele的内容,handData2受影响,因为他们的内容相同(指向了同一个内存)

    System.out.println(handData1.ele); // 返回:2
    System.out.println(handData2.ele); // 返回:2

    handData1 = new HandClass(); // 为handData1开辟一个新的内地址,并将该地址赋值给handData1的内容

    // 直接将handData1和handData2打印出来,返回不相同的内容(地址):handData1的内容
    System.out.println(handData1); // 返回:HandClass@3ced0338
    System.out.println(handData2); // 返回:HandClass@66edc3a2

    handData1.ele = 3; // 此时再次修改handData1.ele的内容,handData2不受影响,因为他们的内容已经不相同(指向了不同的内存)

    System.out.println(handData1.ele); // 返回:3
    System.out.println(handData2.ele); // 返回:2

总结:无论是基本数据类型,还是引用数据类型,赋值操作的实质都是内容赋值。

进入正题

先抛出结论:数组的clone方法,本质是“降维赋值”。即将数组进行降低维度后,开辟一个与之一摸一样的内存空间,然后进行遍历赋值。

(此文不讨论数组的存储模式,对数组存储模式比较薄弱的朋友,请先自行了解)

一维数组:

一维数组降维后是一组变量。

int intArrayA[] = new int[]{1,2,3};
    int intArrayB[] = intArrayA.clone(); // 将intArrayA的克隆赋值给intArrayB
    /**
     * 先对intArrayA进行降维
     * 降维后,变成一组变量:intArrayA[0]、intArrayA[1]、intArrayA[2]
     * 在内存中申请一组与intArrayA类型、长度相同数组: int tmp[] = new int[2];
     * 将变量进行遍历赋值:tmp[0]=intArrayA[0]、tmp[1]=intArrayA[1]、tmp[2]=intArrayA[2]
     * 将数组tmp的内容(地址)返回(注:tmp是一个数组,即属于引用类型)
     * */
    System.out.println(intArrayA[1]);  // 返回:2
    System.out.println(intArrayB[1]);  // 返回:2

    intArrayA[1] = 100;

    System.out.println(intArrayA[1]);  // 返回:100
    System.out.println(intArrayB[1]);  // 返回:2
    /**
     * 上述结论:"无论是基本数据类型,还是引用数据类型,赋值操作的实质都是内容赋值。"
     * intArrayA降维后,intArrayA[0]~intArrayA[2]是一组基本数据类型的变量
     * 赋值的时候,将intArrayA[0]~intArrayA[2]的内容(实际的值)赋值给tmp[0]~tmp[2]
     * 而后tmp[0]~tmp[2]组成的tmp的内容(一个地址)又返回给intArrayB
     * 因此,intArrayB[1]和intArrayA[1]的内容是一致的,他们的内容均是"2"
     * 当我们通过intArrayA[1]进行操作时,仅仅是修改自身的内容,intArrayB[1]不会受到影响
     * */

    System.out.println("--------------------");

    HandClass handArrayA[] = new HandClass[]{new HandClass(),new HandClass(),new HandClass()};
    HandClass handArrayB[] = handArrayA.clone();
    /**
     * 先对handArrayA进行降维
     * 降维后,编程一组变量:handArrayA[0]、handArrayA[1]、handArrayA[2]
     * 在内存中申请一组与handArrayA类型长度、相同数组: HandClass tmp[] = new HandClass[2];
     * 将变量进行遍历赋值:tmp[0]=handArrayA[0]、tmp[1]=handArrayA[1]、tmp[2]=handArrayA[2]
     * 将数组tmp的内容(地址)返回(注:tmp是一个数组,即属于引用类型)
     * */

    System.out.println(handArrayA[1].ele);  // 返回:0 注:此处的0,是实例化时,系统赋与的默认初始值
    System.out.println(handArrayB[1].ele);  // 返回:0

    handArrayA[1].ele = 100;

    System.out.println(handArrayA[1]);  // 返回:HandClass@7b1ddcde
    System.out.println(handArrayB[1]);  // 返回:HandClass@7b1ddcde
    System.out.println(handArrayA[1].ele);  // 返回:100
    System.out.println(handArrayB[1].ele);  // 返回:100
    /**
     * 上述结论:"无论是基本数据类型,还是引用数据类型,赋值操作的实质都是内容赋值。"
     * handArrayA降维后,handArrayA[0]~handArrayA[2]是一组引用类型的变量
     * 赋值的时候,将handArrayA[0]~handArrayA[2]的内容(一个地址)赋值给tmp[0]~tmp[2]
     * 而后tmp[0]~tmp[2]组成的tmp的内容(一个地址)又返回给handArrayB
     * 因此,handArrayB[1]和handArrayA[1]的内容是一致的,他们均指向了同一个内存
     * 当我们通过handArrayA[1]进行操作时(实际是修改其内容对应的实际对象的内容),handArrayB[1](内容所指向的实际对象)也会受到影响
     * */

二维及多维数组:

二维数组降维后是一组数组,数组本身就是引用类型。因此二维及多维的数组的克隆中的赋值,均属于引用类型赋值。

int multIntArrayA[][] = new int[][]{{11,12,13},{21,22,23}};
    int multIntArrayB[][] = multIntArrayA.clone();
    /**
     * 先对multIntArrayA进行降维
     * 降维后,变成一组一维数组:multIntArrayA[0]、multIntArrayA[1]
     * 在内存中申请一组与multIntArray类型、长度相同数组: int tmp[][] = new int[2][3];
     * 将数组进行遍历赋值:tmp[0]=multIntArray[0]、tmp[1]=multIntArray[1],
     * 特别着重说明的事,这里是数组(引用类型)的赋值,而并非数组元素(int类型)的赋值
     * 将数组tmp的内容(地址)返回(注:tmp是一个数组,即属于引用类型)
     * */

    System.out.println(multIntArrayA[0][1]); // 返回:12
    System.out.println(multIntArrayB[0][1]); // 返回:12

    multIntArrayA[0][1] = 66;

    System.out.println(multIntArrayA[0][1]); // 返回:66
    System.out.println(multIntArrayB[0][1]); // 返回:66
    /**
     * 我们注意到,multIntArrayB已经受到了multIntArrayA的影响
     * 因为clone只会降低一个维度后进行遍历赋值,即:将multIntArrayA[0]的内容(一个地址)赋值给multIntArrayB[0]
     * 当我们操作multIntArrayA[0][1]时,实际是操作了multIntArrayA[0]的内容所指向的实际的数组第一个元素的值
     * multIntArrayB[0]保存的内容与multIntArrayA[0]一致,同样指向的是同一个数组
     * 因此multIntArrayB[0][1]全等于multIntArrayA[0][1](变量自身一模一样)
     * 再次也可以明确,clone的降维,只会降低一个维度
     * 若要数组元素也克隆,则需要再次clone,以将维度降低至数组元素
     * */

    multIntArrayB[0] = multIntArrayA[0].clone();

    multIntArrayA[0][1] = 77;

    System.out.println(multIntArrayA[0][1]); // 返回:77
    System.out.println(multIntArrayB[0][1]); // 返回:66
    /**
     * 可以看出,当我们对multIntArrayA[0]进行克隆
     * multIntArrayA[0]降维后,就是一组基本数据类型的变量(int)
     * 因此multIntArrayB[0]中的各个元素(基本数据类型)全等于multIntArrayA[0]中的元素,而是一个"克隆品"
     * 当我们修改multIntArrayA[0][1]后,multIntArrayB[0][1]并不会随之变化
     * 为了加强验证这一现象,我们将"基本数据类型"改为"引用数据类型"
     * 如果我们的猜想没错,进行上述操作后,最后输出的应该也一样是"77",而不是"66"
     * */

    System.out.println("--------------------");

    HandClass multHandArrayA[][] = new HandClass[][]{{new HandClass(),new HandClass(),new HandClass()},{new HandClass(),new HandClass(),new HandClass()}};
    HandClass multHandArrayB[][] = multHandArrayA.clone();



    System.out.println(multHandArrayA[0][1]); // 返回:HandClass@7b1ddcde
    System.out.println(multHandArrayB[0][1]); // 返回:HandClass@7b1ddcde

    multHandArrayA[0][1].ele = 66;

    System.out.println(multHandArrayA[0][1].ele); // 返回:66
    System.out.println(multHandArrayB[0][1].ele); // 返回:66

    multHandArrayB[0] = multHandArrayA[0].clone();

    multHandArrayA[0][1].ele = 77;

    System.out.println(multHandArrayA[0][1].ele); // 返回:77
    System.out.println(multHandArrayB[0][1].ele); // 返回:77
    /**
     * 如果只是进行multHandArrayA的clone,那么"基本数据类型"和"引用数据类型"是一样的
     * 但是,当我们再次对multHandArrayA[0]进行克隆后,效果就不一样了
     * 由于multHandArrayA[0]降维后,是一组引用数据类型的数组
     * 因此multHandArrayB[0]中的各个元素(引用数据类型)的内容与multIntArrayA[0]中对应的元素一致,即指向同一个地址
     * 当我们修改multHandArrayA[0][1]的值(实际修改的是它内容指向的地址对应的实际对象),multHandArrayB[0][1]也会随之变化
     * */

总结:Java中,数组的克隆(clone)只会降一次维,而后开辟一块新的空间,遍历所有元素进行赋值操作。

值得一提

一维数组,由于降维后就是数组的基本元素,因此看起来就像是进行了“深拷贝”,实际是错误的。 只有基本数据类型的一维数组的clone才是“深拷贝”的效果; 引用数据类型的一维数组clone,还需要额外进行“对象拷贝”;

二维或者多维数组可以通过递归方式,进行更低维度(直至降低至一维)的clone,从而达到“深拷贝”的目的。

原文  https://segmentfault.com/a/1190000020345989
正文到此结束
Loading...