深刻淺出,如何更完全地理解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

須要注意的是,無論是基本數據類型,仍是引用數據類型,賦值(=)操做都是將變量自身內容賦值給另外一個變量。惟一不一樣的是,咱們經常使用操做中,基本數據類型是針對自身內容(值)進行的;而引用數據類型則是針對自身內容(地址)的指向進行的。code

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,從而達到「深拷貝」的目的。引用

相關文章
相關標籤/搜索