Java是值傳遞還是引用傳遞,又是怎麼體現的

比花花解语發表於2024-09-07

關於Java是值傳遞還是引用傳遞,可以從程式碼層面來實現一下拿到結果
執行下面的程式碼:

    public static void main(String[] args) {
        int num = 10;
        String name = "Tom";
        modify(num, name);
        System.out.println("第3次列印int:" + num);
        System.out.println("第3次列印String:" + name);
        System.out.println("------------------------------------");
    }

    public static void modify(int n, String str){
        System.out.println("第1次列印int:" + n);
        System.out.println("第1次列印String:" + str);
        System.out.println("------------------------------------");

        // 嘗試在方法內部修改傳進來的引數
        n = 999;
        str = "ABC";
        System.out.println("第2次列印int:" + n);
        System.out.println("第2次列印String:" + str);
        System.out.println("------------------------------------");
    }

列印出來的結果如下:

第1次列印int:10
第1次列印String:Tom
------------------------------------
第2次列印int:999
第2次列印String:ABC
------------------------------------
第3次列印int:10
第3次列印String:Tom
------------------------------------

可以看到無論是基本型別還是引用型別,傳引數進去的時候的值和執行完modify方法後的值是一樣的,也就是第1次列印和第三次列印是一樣的。可是為什麼明明在第2次已經修改成功了,第3次卻又變回去了呢?
嘗試換個方法把引數拿出來,

    public static void main(String[] args) {
        int num = 10;
        String name = "Tom";
        int modifiedNum = modifyAndReturn(num);
        String modifiedName = modifyAndReturn(name);
        System.out.println("列印num:" + num);
        System.out.println("列印name:" + name);
        System.out.println("------------------------------------");
        System.out.println("列印modifiedNum:" + modifiedNum);
        System.out.println("列印modifiedName:" + modifiedName);
    }

    public static int modifyAndReturn(int n){
        System.out.println("modifyAndReturn第1次列印int:" + n);

        // 嘗試在方法內部修改傳進來的引數
        n = 999;
        System.out.println("modifyAndReturn第2次列印int:" + n);
        System.out.println("------------------------------------");
        return n;
    }

    public static String modifyAndReturn(String str){
        System.out.println("modifyAndReturn第1次列印String:" + str);

        // 嘗試在方法內部修改傳進來的引數
        str = "ABC";
        System.out.println("modifyAndReturn第2次列印String:" + str);
        System.out.println("------------------------------------");
        return str;
    }

得到的結果為

modifyAndReturn第1次列印int:10
modifyAndReturn第2次列印int:999
------------------------------------
modifyAndReturn第1次列印String:Tom
modifyAndReturn第2次列印String:ABC
------------------------------------
列印num:10
列印name:Tom
------------------------------------
列印modifiedNum:999
列印modifiedName:ABC

可以看到透過return出來的值,的確是被改變了的,那又是為什麼導致這個改變沒有應用到引數本體呢?修改下程式碼再次測試

public static void main(String[] args) {
        int num = 10;
        String name = "Tom";
        // 列印num和str的地址
        System.out.println("修改前,傳參前:");
        System.out.println(System.identityHashCode(num));
        System.out.println(System.identityHashCode(name));

        System.out.println("---------------------------");
        printAddr(num, name);

        System.out.println("---------------------------");
        System.out.println("修改後,執行完方法後:");
        System.out.println(System.identityHashCode(num));
        System.out.println(System.identityHashCode(name));
    }

    public static void printAddr(int n, String str){
        // 列印n和str的地址
        System.out.println("修改前,傳參後:");
        System.out.println(System.identityHashCode(n));
        System.out.println(System.identityHashCode(str));

        n = 999;
        str = "ABC";

        // 列印n和str的地址
        System.out.println("---------------------------");
        System.out.println("修改後,傳參後:");
        System.out.println(System.identityHashCode(n));
        System.out.println(System.identityHashCode(str));
    }

執行結果如下

修改前,傳參前:
1324119927
990368553
---------------------------
修改前,傳參後:
1324119927
990368553
---------------------------
修改後,傳參後:
1096979270
1078694789
---------------------------
修改後,執行完方法後:
1324119927
990368553

可以看到傳參進來的引數地址是和外部定義的地址是同一個,但是修改之後會指向另一個新的地址,導致原來地址上的資料不會受到影響,這其實是一個保護機制,防止引數傳入方法內被篡改指向。

下面演示引用型別的另一種情況,一些老鐵可能以為是對引用型別本身的修改,其實這是不對的。
先定義一個類Person

class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

	@Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }	
}

執行下面的程式碼,可以看到傳進去的引數的屬性被改變


    public static void main(String[] args) {
        Person person = new Person("Rosy", 24);
        String [] strings = {"AAA", "BBB", "CCC"};
        System.out.println("第1次列印:");
        System.out.println(person);
        System.out.println(Arrays.toString(strings));

        modifyObjAndPrintValue(person, strings);

        System.out.println("---------------------------");
        System.out.println("第4次列印:");
        System.out.println(person);
        System.out.println(Arrays.toString(strings));
    }

    public static void main5(String[] args) {
        Person person = new Person("Rosy", 24);
        String [] strings = {"AAA", "BBB", "CCC"};
        System.out.println("第1次列印:");
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getAge()));
        System.out.println(System.identityHashCode(person.getName()));
        System.out.println(System.identityHashCode(strings));

        modifyObj(person, strings);

        System.out.println("---------------------------");
        System.out.println("第4次列印:");
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getAge()));
        System.out.println(System.identityHashCode(person.getName()));
        System.out.println(System.identityHashCode(strings));
    }

    public static void modifyObjAndPrintValue(Person person, String [] strings){
        System.out.println("---------------------------");
        System.out.println("第2次列印:");
        System.out.println(person);
        System.out.println(Arrays.toString(strings));

        person.setAge(1024);
        person.setName("ABC");
        strings[0] = "XXX";

        System.out.println("---------------------------");
        System.out.println("第3次列印:");
        System.out.println(person);
        System.out.println(Arrays.toString(strings));
    }

執行結果為

第1次列印:
Person{name='Rosy', age=24}
[AAA, BBB, CCC]
---------------------------
第2次列印:
Person{name='Rosy', age=24}
[AAA, BBB, CCC]
---------------------------
第3次列印:
Person{name='ABC', age=1024}
[XXX, BBB, CCC]
---------------------------
第4次列印:
Person{name='ABC', age=1024}
[XXX, BBB, CCC]

從結果可以發現,Person物件的屬性都被修改,String陣列的元素也被修改,說明引數裡對屬性或陣列的修改是會影響物件本身的,具體可以列印地址再檢視一下:


    public static void main(String[] args) {
        Person person = new Person("Rosy", 24);
        String [] strings = {"AAA", "BBB", "CCC"};
        System.out.println("第1次列印:");
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getAge()));
        System.out.println(System.identityHashCode(person.getName()));
        System.out.println(System.identityHashCode(strings));
        System.out.println(System.identityHashCode(strings[0]));

        modifyObjAndPrintAddr(person, strings);

        System.out.println("---------------------------");
        System.out.println("第4次列印:");
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getAge()));
        System.out.println(System.identityHashCode(person.getName()));
        System.out.println(System.identityHashCode(strings));
        System.out.println(System.identityHashCode(strings[0]));
    }


    public static void modifyObjAndPrintAddr(Person person, String [] strings){
        System.out.println("---------------------------");
        System.out.println("第2次列印:");
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getAge()));
        System.out.println(System.identityHashCode(person.getName()));
        System.out.println(System.identityHashCode(strings));
        System.out.println(System.identityHashCode(strings[0]));

        person.setAge(1024);
        person.setName("ABC");
        strings[0] = "XXX";

        System.out.println("---------------------------");
        System.out.println("第3次列印:");
        System.out.println(System.identityHashCode(person));
        System.out.println(System.identityHashCode(person.getAge()));
        System.out.println(System.identityHashCode(person.getName()));
        System.out.println(System.identityHashCode(strings));
        System.out.println(System.identityHashCode(strings[0]));
    }
第1次列印:
990368553
1096979270
1078694789
1831932724
1747585824
---------------------------
第2次列印:
990368553
1096979270
1078694789
1831932724
1747585824
---------------------------
第3次列印:
990368553
1023892928
558638686
1831932724
1149319664
---------------------------
第4次列印:
990368553
2093631819
558638686
1831932724
1149319664

從地址上可以看到,person和strings的地址一直沒有變過。而在引數內部修改的person屬性和陣列元素,會對這部分成員的地址進行修改,並且會應用到物件本體上。

總結下來就是,無論傳的是基本型別還是引用型別,只要在方法內部嘗試改變引數地址的,都只能在方法內部使用,不會影響本體,而在方法內部改變屬性的,會把對應的改變應用到本體上。所以Java是值傳遞的,傳參的時候並不是把本身傳入,而是建立一個副本,當被修改指向的時候不會影響本身,修改屬性由於不會修改本身的地址,因此的時候可以應用到本體上。

相關文章