1. 程式人生 > >從ArrayList說起的JAVA複製與引數傳遞機制

從ArrayList說起的JAVA複製與引數傳遞機制

這兩者都算是java基礎中的基礎,平常寫程式碼可能並沒有過多的去深究它,但這樣容易引發一些不可預知的BUG。

這裡有一個簡單的類,文章中會提到多次。

一個學生類,它有兩個屬性,String型別的name與Integer型別的age。

public class Student {
    private String name;
    private Integer age;

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this
.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }

首先我們有一個List。

        ArrayList<Student> originalList = new ArrayList();
        list1.add(new Student("五更琉璃"
, 15)); list1.add(new Student("高阪桐乃", 14));

1、先說說最簡單粗暴的複製。

        ArrayList<Student> copyList = new ArrayList<>();
        copyList = originalList;

        copyList.set(1,new Student("土間埋",16));

copyList直接獲得originalList的引用

originalList ==>
Student{name='五更琉璃', age=15
} Student{name='土間埋', age=16} copyList ==> Student{name='五更琉璃', age=15} Student{name='土間埋', age=16}

結果如下,我們發現,即使只改變了copyList的元素element,原來的ArrayList也跟著變了。

originalList = {[email protected]531}  size = 2
 0 = {[email protected]533} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]534} "Student{name='土間埋', age=16}"
copyList = {[email protected]531}  size = 2
 0 = {[email protected]533} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]534} "Student{name='土間埋', age=16}"

打個斷點我們可以發現,無論是ArrayList,還是裡面的引用,它們的記憶體地址是完全一樣的。

也就是說,直接賦值 copyList = originalList;的這種方法,很難稱得上是一種複製。

2、使用clone方法進行復制

        ArrayList<Student> copyList = (ArrayList<Student>) originalList.clone();
        copyList.set(1,new Student("土間埋",16));

結果如下:看起來正常了,我們的originalList並沒有因為copyList的set方法而改變。

originalList ==>
Student{name='五更琉璃', age=15}
Student{name='高阪桐乃', age=14}

copyList ==>
Student{name='五更琉璃', age=15}
Student{name='土間埋', age=16}

然而開啟斷點會發現,不是那麼一回事。

我們發現,originalList和copyList的指向確實不同了。
然而List中的元素,指向的還是同一塊地址。

originalList = {[email protected]531}  size = 2
 0 = {[email protected]534} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]535} "Student{name='高阪桐乃', age=14}"
copyList = {[email protected]532}  size = 2
 0 = {[email protected]534} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]539} "Student{name='土間埋', age=16}"

也就是說,如果我們不進行set重新修改元素的指向,而是直接改變元素內的屬性。

        ArrayList<Student> copyList = (ArrayList<Student>) originalList.clone();
        copyList.get(1).setName("土間埋");
        copyList.get(1).setAge(16);

從斷點中我們可以得到我們的預期。originalList 中的第一個元素,也會被改變。

originalList = {[email protected]463}  size = 2
 0 = {[email protected]466} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]467} "Student{name='土間埋', age=16}"
copyList = {[email protected]464}  size = 2
 0 = {[email protected]466} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]467} "Student{name='土間埋', age=16}"

從這裡可以看出,ArrayList提供的clone方法,實際上是一種淺複製。

也就是它不是一種遞迴複製。

它只是改變了頂層,copyList 的引用。

我們看看原始碼

 public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

原始碼中,先是clone了一個新的ArrayList:v

然後將原ArrayList中的資料直接複製到了新的ArrayList中。

再看看ArrayList中的add與addAll方法,實際上也是如出一轍。

   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

它們只是把元素的引用複製了一遍。也就是說呼叫clone、add、addAll方法,當改變元素中的屬性時,新List中的元素也會跟著改變。

java的引數傳遞也是如此,java中,引數的傳遞都是值傳遞。當一個物件例項作為一個引數被傳遞到方法中時,引數的值就是該物件的引用一個副本。

當這個副本的屬性在方法內部被改變時,這個副本的正本也會被改變。

那麼就針對當前業務來說,如何實現深克隆?

        ArrayList<Student> copyList = new ArrayList<>();
        for (Student student : originalList) {
            copyList.add(student.clone());
        }

        copyList.get(1).setName("土間埋");
        copyList.get(1).setAge(16);

粗暴實現:我們發現它們的地址已經完全不同了。

originalList = {[email protected]464}  size = 2
 0 = {[email protected]467} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]468} "Student{name='高阪桐乃', age=14}"
copyList = {[email protected]465}  size = 2
 0 = {[email protected]472} "Student{name='五更琉璃', age=15}"
 1 = {[email protected]473} "Student{name='土間埋', age=16}"

2018年7月17日 補充,偶然回顧這篇文章,其實還有一種又快又好的深複製方法:序列化,將物件轉成進行序列化再反序列化即可,效率還是挺高的(不適用於複雜物件)..

相關推薦

ArrayListJAVA複製引數傳遞機制

這兩者都算是java基礎中的基礎,平常寫程式碼可能並沒有過多的去深究它,但這樣容易引發一些不可預知的BUG。 這裡有一個簡單的類,文章中會提到多次。 一個學生類,它有兩個屬性,String型別的name與Integer型別的age。 public cl

java引用引數傳遞

網上都在講引數傳遞是一種拷貝,拷貝的變化不會影響原值,當然這是對的。還有一種說法,叫JAVA裡只有一種引數傳遞方式,值傳遞,這也是對的。但是我總覺得這些說法容易讓新人犯迷糊,因為有一個很常見的現象,很多時候拷貝也能改變原始物件的屬性。所以有些人又把引數傳遞跟基本型別傳遞分開

Java基礎(五)Java中的引數傳遞機制

通過前一篇文章的介紹,我們從整體上明白了,Java類中變數的差異性、不同變數在記憶體中的儲存位置,以及變數的生命週期等。今天,我們來看一下Java中引數傳遞的機制。 形參:方法宣告時包含的引數宣告 實參:呼叫方法時,實際傳給形參的引數值 Java方法的引數傳遞機制:

JAVA的值傳遞引數傳遞

3.引用傳遞和值傳遞 值傳遞:方法呼叫時,實際引數把它的值傳遞給對應的形式引數,函式接收的是原始值的一個copy,此時記憶體中存在兩個相等的基本型別,即實際引數和形式引數,後面方法中的操作都是對形參這個值的修改,不影響實際引數的值。 引用傳遞:方法呼叫時,實際引數的引用(地址,而不是引數的值)被傳遞給方法中

Java 中的引數傳遞

       Java 中的引數傳遞方式是傳遞值, 也稱為”值傳遞”。 當引數變元是一個簡單型別時,值傳遞意味著這個方法不能改變引數變元的值,即方法中變數所做的改變在方法外都是不可見的,也可以說不起作用; 當引數變元是

java方法之間引數傳遞java 程式設計語言對物件採用的是引用呼叫嗎)

java方法之間引數傳遞 基礎型別值傳遞 public static void tripleValue(double x){ x = 3*x; } double value = 10; tripleValue(value); System.out.prin

java——引用型別引數傳遞

// 引用型別引數 public class test1 { int num = 1; public static void main(String[] args) { int[] arr = new int[]{19, 30}; System.out.print("change_p

九、java基礎之引數傳遞(值傳遞

一、值傳遞/*程式在執行過程中,引數傳遞問題: 1.傳遞資料是基本資料型別 2.傳遞資料是引用資料型別 */1.以下程式是傳遞基本資料型別 public class ClassTest04 { public static void m1(int i){ i++;

Linux 常用的引數傳遞有關的系統變數

變數名 #n   表示傳遞給指令碼的第n個引數,例如$1 表示第1個引數,$2表示2個引數... $# 命令列引數的個數 $0 當前指令碼的名稱 $* 以“引數1  引數2 引數3....”的形式返回所有引數的值(將所有引數一一個字串的形式返回) [ema

ArrayListJava泛型

從ArrayList看Java泛型 一、知識點 1、Java 泛型 2、Java 多型 3、Java ArrayList原始碼 二、個人理解 1、泛型更形式面向物件思想多型的擴充套件,它的使用範圍更多的針對於類型別的引數、方法引數、方法返回值等等,它可以讓程式的程式碼更簡潔

Java - 引數傳遞機制

Java 中,方法中的所有引數都是“值傳遞”,也就是“傳遞的是值的副本”.也就是說,我們得到的是“原引數的影印件,而不是原件”.因此,影印件改變不會影響原件. 1 基本資料型別引數的傳值 傳遞的是值的副本,副本改變不會影響原件 2 引用型別引數的傳值 傳遞的是值的副本.但是應用型別指的是

java中的引數傳遞傳遞,引用傳遞

引數是按值而不是按引用傳遞的說明 Java 應用程式有且僅有的一種引數傳遞機制,即按值傳遞。 在 Java 應用程式中永遠不會傳遞物件,而只傳遞物件引用。因此是按引用傳遞物件。Java 應用程式按引用傳遞物件這一事實並不意味著 Java 應用程式按引用傳遞引數。引數可以是物件引用,而 J

Java中的引數傳遞

Java中沒有真正的引用傳遞 只有值傳遞! 傳引用引數指的還是原來的那個引用,但是Java裡面引數型別是物件時是複製了原來的引用到一塊新的記憶體,兩者之間沒有關係 1:按值傳遞是什麼 指的是在方法呼叫時,傳遞的引數是按值的拷貝傳遞。示例如下:

java中的引數傳遞-值傳遞、引用傳遞

引數是按值而不是按引用傳遞的說明 Java 應用程式有且僅有的一種引數傳遞機制,即按值傳遞。 在 Java 應用程式中永遠不會傳遞物件,而只傳遞物件引用。因此是按引用傳遞物件。Java 應用程

關於java呼叫webservice引數傳遞為空問題

    通過Myeclipse10.0 jdk1.7呼叫VS2012 webService遇到java客戶端引數傳遞不過去的問題,搞了一下午終於出結果了,其實網上好多方法都只是一部分,需要綜合一下。 客戶端我是用import org.apache.axis.client.

關於Java物件作為引數傳遞是傳值還是傳引用的問題

前言   在Java中,當物件作為引數傳遞時,究竟傳遞的是物件的值,還是物件的引用,這是一個飽受爭議的話題。若傳的是值,那麼函式接收的只是實參的一個副本,函式對形參的操作並不會對實參產生影響;若傳的是引用,那麼此時對形參的操作則會影響到實參。   首先我們來

JAVA中的引數傳遞原則

JAVA中的引數傳遞全是以值傳遞的 總的來說,簡單概括Java中進行方法呼叫的時候傳遞引數時,遵循值傳遞的原則: 1)基本資料型別,傳遞的是資料的拷貝 。“值傳遞”,在這種方式下,被呼叫物件對新資料的改變不影響源資料的取值,如基本型別的傳遞 //例1 void met

誤人子弟篇之C語言函式返回值引數傳遞

寫在開頭以免看到結尾你,此篇部落格純屬瞎扯,看看就可以了,不要當真哦! 如果搞過彙編,寫過子程式,那麼你就不用看了,因為看到最後你會發現,在彙編中你有很多方法去返回值,傳遞引數,而在高階語言中,編譯器只是選擇了其中的一種而已,而這篇部落格也寫的毫無邏輯,簡直喪盡天良,草菅人

java方法中引數傳遞小結

很簡單的java基礎,竟然有點遺忘了,是在不改,爛筆頭記錄一下。   1、當物件作為引數傳遞時,傳遞的是物件的引用,也就是物件的地址。值實時更新!(一般專案中常見的物件作為引數很多,記住物件會在方法中實時更新!!!因為方法中操作的是同一個物件) public clas

JAVA介面作為引數傳遞

可以將介面型別的引數作為方法引數,在實際是使用時可以將實現了介面的類傳遞給方法,後方法或按照重寫的原則執行,實際呼叫的是實現類中的方法程式碼體,這樣便根據傳進入的引數的不同而實現不同的功能。重要的是,當我以後還有另外一個物件並且擁有接受說生命的方法的時候的時候,我們不必須原類