陷阱將變數視為物件
沒有 Java 變數表示物件。
String foo; // NOT AN OBJECT
任何 Java 陣列都不包含物件。
String bar[] = new String[100]; // No member is an object.
如果你錯誤地將變數視為物件,Java 語言的實際行為會讓你感到驚訝。
-
對於具有基本型別的 Java 變數(例如
int
或float
),變數儲存值的副本。原始值的所有副本都是難以區分的; 即第一個只有一個int
值。原始值不是物件,它們的行為不像物件。 -
對於具有引用型別(類或陣列型別)的 Java 變數,該變數包含引用。參考文獻的所有副本都難以區分。引用可能指向物件,或者它們可能是
null
,這意味著它們指向沒有物件。但是,它們不是物件,它們的行為不像物件。
在任何一種情況下,變數都不是物件,並且在任何一種情況下它們都不包含物件。它們可能包含物件的引用,但這就是說不同的東西。
示例類
以下示例使用此類,它表示 2D 空間中的一個點。
public final class MutableLocation {
public int x;
public int y;
public MutableLocation(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object other) {
if (!(other instanceof MutableLocation) {
return false;
}
MutableLocation that = (MutableLocation) other;
return this.x == that.x && this.y == that.y;
}
}
這個類的一個例項是一個物件,它有兩個欄位 x
和 y
,它們的型別為 int
。
我們可以有很多 MutableLocation
類的例項。有些將代表 2D 空間中的相同位置; 即 x
和 y
的相應值將匹配。其他人將代表不同的地點。
多個變數可以指向同一個物件
MutableLocation here = new MutableLocation(1, 2);
MutableLocation there = here;
MutableLocation elsewhere = new MutableLocation(1, 2);
在上面,我們已經宣告瞭三個變數 here
,there
和 elsewhere
,它們可以儲存對 MutableLocation
物件的引用。
如果你(錯誤地)將這些變數視為物件,那麼你可能會誤讀這些語句:
- 將位置“[1,2]”複製到
here
- 將位置“[1,2]”複製到
there
- 將位置“[1,2]”複製到
elsewhere
從那以後,你可能會推斷我們在三個變數中有三個獨立的物件。實際上,上面只建立了兩個物件。變數 here
和 there
實際上指的是同一個物件。
我們可以證明這一點。假設變數宣告如上:
System.out.println("BEFORE: here.x is " + here.x + ", there.x is " + there.x +
"elsewhere.x is " + elsewhere.x);
here.x = 42;
System.out.println("AFTER: here.x is " + here.x + ", there.x is " + there.x +
"elsewhere.x is " + elsewhere.x);
這將輸出以下內容:
BEFORE: here.x is 1, there.x is 1, elsewhere.x is 1
AFTER: here.x is 42, there.x is 42, elsewhere.x is 1
我們為 here.x
分配了一個新值,它改變了我們通過 there.x
看到的值。它們指的是同一個物件。但是我們通過 elsewhere.x
看到的價值沒有改變,所以 elsewhere
必須引用不同的物件。
如果變數是一個物件,則賦值 here.x = 42
不會改變 there.x
。
等於運算子不測試兩個物件是否相等
將等於(==
)運算子應用於引用值測試值是否指向同一物件。它並沒有測試兩個(不同的)物件是否在直觀的感覺相等。
MutableLocation here = new MutableLocation(1, 2);
MutableLocation there = here;
MutableLocation elsewhere = new MutableLocation(1, 2);
if (here == there) {
System.out.println("here is there");
}
if (here == elsewhere) {
System.out.println("here is elsewhere");
}
這將列印這裡有,但它不會列印這裡是其他地方。 (here
和 elsewhere
中的引用是針對兩個不同的物件。)
相比之下,如果我們呼叫上面實現的 equals(Object)
方法,我們將測試兩個 MutableLocation
例項是否具有相同的位置。
if (here.equals(there)) {
System.out.println("here equals there");
}
if (here.equals(elsewhere)) {
System.out.println("here equals elsewhere");
}
這將列印兩條訊息。特別是,here.equals(elsewhere)
返回 true
,因為我們選擇了兩個 MutableLocation
物件相等的語義標準。
方法呼叫根本不傳遞物件
Java 方法呼叫使用 pass by value 1 來傳遞引數並返回結果。
將參考值傳遞給方法時,實際上是按值傳遞物件的引用,這意味著它正在建立物件引用的副本。
只要兩個物件引用仍然指向同一個物件,你就可以從任一引用修改該物件,這就是導致某些物件混淆的原因。
但是,你沒有通過引用 2 傳遞物件。區別在於,如果將物件引用副本修改為指向另一個物件,則原始物件引用仍將指向原始物件。
void f(MutableLocation foo) {
foo = new MutableLocation(3, 4); // Point local foo at a different object.
}
void g() {
MutableLocation foo = MutableLocation(1, 2);
f(foo);
System.out.println("foo.x is " + foo.x); // Prints "foo.x is 1".
}
你也沒有傳遞物件的副本。
void f(MutableLocation foo) {
foo.x = 42;
}
void g() {
MutableLocation foo = new MutableLocation(0, 0);
f(foo);
System.out.println("foo.x is " + foo.x); // Prints "foo.x is 42"
}
1 - 在 Python 和 Ruby 等語言中,術語通過共享傳遞是物件/引用的按值傳遞的首選。
2 - 術語通過引用傳遞或通過引用呼叫在程式語言術語中具有非常特定的含義。實際上,它意味著你傳遞變數或陣列元素的地址,以便當被呼叫的方法為形式引數賦值時,它會更改原始變數中的值。Java 不支援這一點。有關傳遞引數的不同機制的更完整描述,請參閱 https://en.wikipedia.org/wiki/Evaluation_strategy 。