“別名”意味著多個句柄都試圖指向同一個對象,就象前面的例子展示的那樣。若有人向那個對象裡寫入一點什麼東西,就會產生別名問題。若其他句柄的所有者不希望那個對象改變,恐怕就要失望了。這可用下面這個簡單的例子說明:
//: Alias1.java // Aliasing two handles to one object public 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: 7 y: 7 Incrementing x x: 8 y: 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)。所以倘若創建一個會修改自己參數的方法,必須向用戶明確地指出這一情況,並警告使用那個方法可能會有的後果以及它的潛在威脅。由於存在這些混淆和缺陷,所以應該盡量避免改變參數。
若需在一個方法調用期間修改一個參數,且不打算修改外部參數,就應在自己的方法內部制作一個副本,從而保護那個參數。本章的大多數內容都是圍繞這個問題展開的。