這章想要聊聊的點有兩個。
在初學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;
這三行程式碼做了以下事:
這是傳遞值的例子,接下來是傳遞參考的例子。
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的一些特性,那我們明天見吧。