iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0
Software Development

週日時在做什麼?有沒有空?可以來寫SpringBoot嗎?系列 第 2

[Day 2] Call by value、Call by reference?=與==與equals的混亂圓舞曲!

  • 分享至 

  • xImage
  •  

這章想要聊聊的點有兩個。

  1. Java Call By Value與Call By Reference是什麼?
  2. Java =、==、equals() 之間的差別以及如何使用?

在初學Java時,有一些Java的觀念是你必須要認識的,像是 基本型別 (Primitive Type)與參考型別(Reference Type),這兩個的最主要的差別在,當變數被改變時,基本型別被改變的是值本身,而參考型別改變的是參考的對象。

Call By Value可以參考以下例子

class Student {
    int schoolId;
    Integer age;
    String name;
    char rank;
    Student(int schoolId, Integer age, String name, char rank) {
        this.schoolId = schoolId;
        this.age = age;
        this.name = name;
        this.rank = rank;
    }

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

private void replaceByHacker(int schoolId, Integer age, String name, char rank) {
    schoolId = 99999;
    age = 9999;
    name = "Stolen";
    rank = 'F';
}

// 運行程式碼
Student student = new Student(1,14,"John",'B');
System.out.println(student);
replaceByHacker(student.schoolId,student.age,student.name,student.rank);
System.out.println(student);

//output
schoolId=1, age=14, name=John, rank=B
schoolId=1, age=14, name=John, rank=B

比方說,我們有一個學生類別,我們定義它之後,先印出學生的資料,接著執行replaceByHacker()方法,想要竄改學生資料,但最後學生的資料並沒有被改變。

原因是傳入的參數是基本型別,也就是傳入的是”值”而不是”位址”,因此實際上在方法內部被更改後,也不會影響先前的變數。

接著我們來看另外一個例子

private void replaceByHacker2(Student student) {
  student.age = 777;
}

// 運行程式碼
System.out.println(student);
replaceByHacker2(student);
System.out.println(student);
  
//output
schoolId=1, age=14, name=John, rank=B
schoolId=1, age=777, name=John, rank=B

在這個例子中,你可以在方法中更改student這個物件的屬性,但對物件本身也是無法被更改的,比方說你不能在方法中把student指定為null。

我自己的理解是,方法中引用的這些參數都只是一個代理的對象,跟資料實際的記憶體位址沒有關係。Java本身也無法傳入記憶體位址,因此並沒有沒有Call By Reference的概念。
至於哪些屬於基本型別,哪些屬於參考型別的,可以看看這篇文章:https://chriskang028.medium.com/%E7%82%BA%E4%BB%80%E9%BA%BC%E8%B3%87%E6%96%99%E5%9E%8B%E5%88%A5%E6%9C%83%E5%BD%B1%E9%9F%BF%E8%B3%87%E6%96%99%E5%AD%98%E5%8F%96-%E6%B7%BA%E8%AB%87-primitive-type-%E8%88%87-reference-type-%E7%9A%84%E5%B7%AE%E7%95%B0-a0a4766a801

綜合以上範例,我們可以得出一道結論。針對基本型別中 = 所傳遞的是值,而針對參考型別 = 所傳遞的是值的參考,為了更好地說明這件事,我們再舉個例子:

int a = 10;
int k = 10;
int b = 5;
b=a;

這三行程式碼做了以下事:

  1. JVM在記憶體中宣告一個空間放了10,並把10指定給a
  2. 把10指定給k
  3. JVM在記憶體中宣告一個空間放了5,並把5指定給b。
  4. 把a存放的10指定給b
  5. a,b等於10

這是傳遞值的例子,接下來是傳遞參考的例子。

String a = new String("Some");
String b = a;

System.out.println("is a equal b ?" + (a == b)); // output: true
String c = new String("Car");
String d = new String("Car");
System.out.println("is c equal d ?" + (c == d)); // output: false
System.out.println("is c equal d ?" + (c.equals(d))); // output: true

前面都一樣,差別只是在第二行a將它的參考給了b,所以a與b此時指向了同一個位置(記憶體位址)。
繼續往下看,值得一提的是c d兩個變數,兩個都宣告為同一個”Car”,但是因為c d兩個使用new String()方法宣告,並擁有各自的記憶體位址,且兩者並沒有指向同一個參考。所以 == 比較的結果是 false。
同時,你也可以得知 == 並不是值的比較,而是判斷兩個變數是否指向同一個位址。
所以,若要比較兩個字串實際上是否相同,則要使用.equals()。
基本上,非Primitive Type的型別我都不建議使用 == 作為判斷是否相等的依據,因為不準。

再來我們來看下一部分

Student student = new Student(1,14,"John",'B');
Student copyStudent = new Student(1,14,"John",'B');
System.out.println(student == copyStudent); //output: false
System.out.println(student.equals(copyStudent)); //output: false

從剛剛我們可以得知==比較的是記憶體位址,因此第三行為false是可理解的,但我想你一定會好奇為甚麼第四行不是true呢?這個原因是因為物件本身若沒有實作.equals()方法時,預設會套用Object的.equals(),也就是以下程式。

public boolean equals(Object obj) {
    return (this == obj);
}

恭喜你回到原點,他結果還是使用== 去比較,所以,這也是為很多物件在建立後必須要複寫.equals()的原因,因為不準,之所以我會說不準的原因,是因為並不是所有時候都會呈現錯誤的結果。在一些複雜的情況下,可能你並不知道兩個物件的記憶體位址是否同源。
所以在實務上,若需要比較兩者物件,必須要額外覆寫成我們需要的邏輯。

class Student {
        int schoolId;
        Integer age;
        String name;
        char rank;
        Student(int schoolId, Integer age, String name, char rank) {
            this.schoolId = schoolId;
            this.age = age;
            this.name = name;
            this.rank = rank;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
		@Override
        public boolean equals(Object that) {
            return this.schoolId == ((Student) that).schoolId &&
                    Objects.equals(this.age, ((Student) that).age) &&
                    Objects.equals(this.name, ((Student) that).name) &&
                    this.rank == ((Student) that).rank;
        }
    }

以上是一些Java的觀念,我希望這些內容是簡單好懂的,若有錯誤的點也歡迎在下方指正,一同學習成長。
下一回也會繼續談有關Java的一些特性,那我們明天見吧。


上一篇
[DAY 1] 2024了,還學Java嗎?
下一篇
[Day 3] 所以我說那個hashCode()到底是甚麼鬼?
系列文
週日時在做什麼?有沒有空?可以來寫SpringBoot嗎?15
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言