“別名”意味著多個控制代碼都試圖指向同一個對象,就象前面的例子展示的那樣。若有人向那個對象裡寫入一點什麼東西,就會產生別名問題。若其他控制代碼的所有者不希望那個對象改變,恐怕就要失望了。這可用下面這個簡單的例子說明:
//: Alias1.java// Aliasing two handles to one objectpublic class Alias1 { int i; Alias1(int ii) { i = ii; } public static void main(String[] args) { Alias1 x = new Alias1(7); Alias1 y = x; // Assign the handle System.out.println("x: " + x.i); System.out.println("y: " + y.i); System.out.println("Incrementing x"); x.i++; System.out.println("x: " + x.i); System.out.println("y: " + y.i); }} ///:~
對下面這行:
Alias1 y = x; // Assign the handle
它會建立一個Alias1控制代碼,但不是把它分配給由new建立的一個新鮮對象,而是分配給一個現有的控制代碼。所以控制代碼x的內容——即對象x指向的地址——被分配給y,所以無論x還是y都與相同的對象串連起來。這樣一來,一旦x的i在下述語句中增值:
x.i++;
y的i值也必然受到影響。從最終的輸出就可以看出:
x: 7y: 7Incrementing xx: 8y: 8
此時最直接的一個解決辦法就是乾脆不這樣做:不要有意將多個控制代碼指向同一個範圍內的同一個對象。這樣做可使代碼更易理解和調試。然而,一旦準備將控制代碼作為一個自變數或參數傳遞——這是Java設想的正常方法——別名問題就會自動出現,因為建立的本地控制代碼可能修改“外部對象”(在方法範圍之外建立的對象)。下面是一個例子:
//: Alias2.java// Method calls implicitly alias their// arguments.public class Alias2 { int i; Alias2(int ii) { i = ii; } static void f(Alias2 handle) { handle.i++; } public static void main(String[] args) { Alias2 x = new Alias2(7); System.out.println("x: " + x.i); System.out.println("Calling f(x)"); f(x); System.out.println("x: " + x.i); }} ///:~
輸出如下:
x: 7
Calling f(x)
x: 8
方法改變了自己的參數——外部對象。一旦遇到這種情況,必須判斷它是否合理,使用者是否願意這樣,以及是不是會造成問題。
通常,我們調用一個方法是為了產生傳回值,或者用它改變為其調用方法的那個對象的狀態(方法其實就是我們向那個對象“發一條訊息”的方式)。很少需要調用一個方法來處理它的參數;這叫作利用方法的“副作用”(Side Effect)。所以倘若建立一個會修改自己參數的方法,必須向使用者明確地指出這一情況,並警告使用那個方法可能會有的後果以及它的潛在威脅。由於存在這些混淆和缺陷,所以應該盡量避免改變參數。
若需在一個方法調用期間修改一個參數,且不打算修改外部參數,就應在自己的方法內部製作一個副本,從而保護那個參數。本章的大多數內容都是圍繞這個問題展開的。