文章目錄
- On Java 8
-
- 第四章 運算符-指派
- 附錄: 對象傳遞和傳回
-
- 傳遞引用
- 本地拷貝
- 控制克隆
- 不可變類
- 本章小結
On Java 8
《On Java 8》中的内容
指派
對象傳遞和傳回
了解:值傳遞和引用傳遞
第四章 運算符-指派
指派
運算符的指派是由符号 = 完成的。它代表着擷取 = 右邊的值并賦給左邊的變量。右邊可以是任何常量、變量或者可産生一個傳回值的表達式。但左邊必須是一個明确的、已命名的變量。也就是說,必須要有一個實體的空間來存放右邊的值。舉個例子來說,可将一個常數賦給一個變量(A = 4),但不可将任何東西賦給一個常數(比如不能 4 =A)。
(值傳遞和引用傳遞:)基本類型的指派都是直接的,而不像對象,賦予的隻是其記憶體的引用。舉個例子,a = b ,如果 b 是基本類型,那麼指派操作會将 b 的值複制一份給變量 a,此後若 a 的值發生改變是不會影響到 b 的。作為一名程式員,這應該成為我們的常識。
如果是為對象指派,那麼結果就不一樣了。對一個對象進行操作時,我們實際上操作的是它的引用。是以我們将右邊的對象賦予給左邊時,賦予的隻是該對象的引用。此時,兩者指向的堆中的對象還是同一個。代碼示例:
// operators/Assignment.java
// Assignment with objects is a bit tricky*
class Tank {
int level;
}
public class Assignment {
public static void main(String[] args) {
Tank t1 = **new** Tank();
Tank t2 = **new** Tank();
t1.level = 9;
t2.level = 47;
System.out.println("1: t1.level: " + t1.level +", t2.level: " + t2.level);
t1 = t2;
System.out.println("2: t1.level: " + t1.level +", t2.level: " + t2.level);
t1.level = 27;
System.out.println("3: t1.level: " + t1.level +", t2.level: " + t2.level);
}
}
輸出結果:
1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27
這是一個簡單的 Tank 類,在 main() 方法建立了兩個執行個體對象。兩個對象的 level屬性分别被賦予不同的值。然後,t2 的值被賦予給 t1。在許多程式設計語言裡,預期的結果是 t1 和 t2 的值會一直相對獨立。但是,在 Java 中,由于賦予的隻是對象的引用,改變 t1 也就改變了 t2。這是因為 t1 和 t2 此時指向的是堆中同一個對象(引用傳遞)。(t1 原始對象的引用在 t2 指派給其時丢失,它引用的對象會在垃圾回收時被清理)。
這種現象通常稱為别名(aliasing),這是 Java 處理對象的一種基本方式。但是假若你不想出現這裡的别名引起混淆的話,你可以這麼做。代碼示例:
較之前的做法,這樣做保留了兩個單獨的對象,而不是丢棄一個并将 t1 和 t2 綁定到同一個對象。但是這樣的操作有點違背 Java 的設計原則。對象的指派是個需要重視的環節,否則你可能收獲意外的 “驚喜”。
// 方法調用中的别名現象
//當我們把對象傳遞給方法時,會發生别名現象。
// operators/PassObject.java
// 正在傳遞的對象可能不是你之前使用的
class Letter {
char c;
}
public class PassObject {
static void f(Letter y) {
y.c = 'z';
}
public static void main(String[] args) {
Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: " + x.c);
f(x);
System.out.println("2: x.c: " + x.c);
}
}
輸出結果:
1: x.c: a
2: x.c: z
在許多程式設計語言中,方法 f() 似乎會在内部複制其參數 Letter y。但是一旦傳遞了一個引用,那麼實際上 y.c =‘z’; 是在方法 f() 之外改變對象。别名現象以及其解決方案是個複雜的問題,在附錄中有包含:對象傳遞和傳回。意識到這一點,我們可以警惕類似的陷阱。
附錄: 對象傳遞和傳回
到現在為止,你已經對 “傳遞” 對象實際上是傳遞引用這一想法想法感到滿意。
在許多程式設計語言中,你可以使用該語言的 “正常” 方式來傳遞對象,并且大多數情況下一切正常。但是通常會出現這種情況,你必須做一些不平常的事情,突然事情變得更加複雜。Java 也不例外,當您傳遞對象并對其進行操作時,準确了解正在發生的事情很重要。本附錄提供了這種見解。
提出本附錄問題的另一種方法是,如果你之前使用類似 C++ 的程式設計語言,則是 “Java 是否有指針?” Java 中的每個對象辨別符(除原語外)都是這些指針之一,但它們的用法是不僅受編譯器的限制,而且受運作時系統的限制。換一種說法,Java 有指針,但沒有指針算法。這些就是我一直所說的 “引用”,您可以将它們視為 “安全指針”,與國小的安全剪刀不同-它們不敏銳,是以您不費吹灰之力就無法傷害自己,但是它們有時可能很乏味。
傳遞引用
當你将引用傳遞給方法時,它仍指向同一對象。一個簡單的實驗示範了這一點:
// references/PassReferences.java
public class PassReferences {
public static void f(PassReferences h) {
System.out.println("h inside f(): " + h);
}
public static void main(String[] args) {
PassReferences p = new PassReferences();
System.out.println("p inside main(): " + p);
f(p);
}
}
/* Output:
p inside main(): [email protected]
h inside f(): [email protected]42
*/
方法 toString() 在列印語句中自動調用,并且 PassReferences 直接從 Object繼承而無需重新定義 toString()。是以,使用的是 Object 的 toString()版本,它列印出對象的類,然後列印出該對象所在的位址(不是引用,而是實際的對象存儲)。