至此,我們已基本理解了內部類的典型用途。對那些涉及內部類的代碼,通常表達的都是“單純”的內部類,非常簡單,且極易理解。然而,內部類的設計非常全面,不可避免地會遇到它們的其他大量用法——假若我們在一個方法甚至一個任意的作用域內創建內部類。有兩方面的原因促使我們這樣做:
(1) 正如前面展示的那樣,我們准備實現某種形式的接口,使自己能創建和返回一個句柄。
(2) 要解決一個復雜的問題,並希望創建一個類,用來輔助自己的程序方案。同時不願意把它公開。
在下面這個例子裡,將修改前面的代碼,以便使用:
(1) 在一個方法內定義的類
(2) 在方法的一個作用域內定義的類
(3) 一個匿名類,用於實現一個接口
(4) 一個匿名類,用於擴展擁有非默認構建器的一個類
(5) 一個匿名類,用於執行字段初始化
(6) 一個匿名類,通過實例初始化進行構建(匿名內部類不可擁有構建器)
所有這些都在innerscopes包內發生。首先,來自前述代碼的通用接口會在它們自己的文件裡獲得定義,使它們能在所有的例子裡使用:
//: Destination.java
package c07.innerscopes;
interface Destination {
String readLabel();
} ///:~
由於我們已認為Contents可能是一個抽象類,所以可采取下面這種更自然的形式,就象一個接口那樣:
//: Contents.java
package c07.innerscopes;
interface Contents {
int value();
} ///:~
盡管是含有具體實施細節的一個普通類,但Wrapping也作為它所有衍生類的一個通用“接口”使用:
//: Wrapping.java
package c07.innerscopes;
public class Wrapping {
private int i;
public Wrapping(int x) { i = x; }
public int value() { return i; }
} ///:~
在上面的代碼中,我們注意到Wrapping有一個要求使用自變量的構建器,這就使情況變得更加有趣了。
第一個例子展示了如何在一個方法的作用域(而不是另一個類的作用域)中創建一個完整的類:
//: Parcel4.java
// Nesting a class within a method
package c07.innerscopes;
public class Parcel4 {
public Destination dest(String s) {
class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
}
} ///:~
PDestination類屬於dest()的一部分,而不是Parcel4的一部分(同時注意可為相同目錄內每個類內部的一個內部類使用類標識符PDestination,這樣做不會發生命名的沖突)。因此,PDestination不可從dest()的外部訪問。請注意在返回語句中發生的上溯造型——除了指向基礎類Destination的一個句柄之外,沒有任何東西超出dest()的邊界之外。當然,不能由於類PDestination的名字置於dest()內部,就認為在dest()返回之後PDestination不是一個有效的對象。
下面這個例子展示了如何在任意作用域內嵌套一個內部類:
//: Parcel5.java
// Nesting a class within a scope
package c07.innerscopes;
public class Parcel5 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// Can't use it here! Out of scope:
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track() { internalTracking(true); }
public static void main(String[] args) {
Parcel5 p = new Parcel5();
p.track();
}
} ///:~
TrackingSlip類嵌套於一個if語句的作用域內。這並不意味著類是有條件創建的——它會隨同其他所有東西得到編譯。然而,在定義它的那個作用域之外,它是不可使用的。除這些以外,它看起來和一個普通類並沒有什麼區別。
下面這個例子看起來有些奇怪:
//: Parcel6.java
// A method that returns an anonymous inner class
package c07.innerscopes;
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
} ///:~
cont()方法同時合並了返回值的創建代碼,以及用於表示那個返回值的類。除此以外,這個類是匿名的——它沒有名字。而且看起來似乎更讓人摸不著頭腦的是,我們准備創建一個Contents對象:
return new Contents()
但在這之後,在遇到分號之前,我們又說:“等一等,讓我先在一個類定義裡再耍一下花招”:
return new Contents() {
private int i = 11;
public int value() { return i; }
};
這種奇怪的語法要表達的意思是:“創建從Contents衍生出來的匿名類的一個對象”。由new表達式返回的句柄會自動上溯造型成一個Contents句柄。匿名內部類的語法其實要表達的是:
class MyContents extends Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();
在匿名內部類中,Contents是用一個默認構建器創建的。下面這段代碼展示了基礎類需要含有自變量的一個構建器時做的事情:
//: Parcel7.java
// An anonymous inner class that calls the
// base-class constructor
package c07.innerscopes;
public class Parcel7 {
public Wrapping wrap(int x) {
// Base constructor call:
return new Wrapping(x) {
public int value() {
return super.value() * 47;
}
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
}
} ///:~
也就是說,我們將適當的自變量簡單地傳遞給基礎類構建器,在這兒表現為在“new Wrapping(x)”中傳遞x。匿名類不能擁有一個構建器,這和在調用super()時的常規做法不同。
在前述的兩個例子中,分號並不標志著類主體的結束(和C++不同)。相反,它標志著用於包含匿名類的那個表達式的結束。因此,它完全等價於在其他任何地方使用分號。
若想對匿名內部類的一個對象進行某種形式的初始化,此時會出現什麼情況呢?由於它是匿名的,沒有名字賦給構建器,所以我們不能擁有一個構建器。然而,我們可在定義自己的字段時進行初始化:
//: Parcel8.java
// An anonymous inner class that performs
// initialization. A briefer version
// of Parcel5.java.
package c07.innerscopes;
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
} ///:~
若試圖定義一個匿名內部類,並想使用在匿名內部類外部定義的一個對象,則編譯器要求外部對象為final屬性。這正是我們將dest()的自變量設為final的原因。如果忘記這樣做,就會得到一條編譯期出錯提示。
只要自己只是想分配一個字段,上述方法就肯定可行。但假如需要采取一些類似於構建器的行動,又應怎樣操作呢?通過Java 1.1的實例初始化,我們可以有效地為一個匿名內部類創建一個構建器:
//: Parcel9.java
// Using "instance initialization" to perform
// construction on an anonymous inner class
package c07.innerscopes;
public class Parcel9 {
public Destination
dest(final String dest, final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395F);
}
} ///:~
在實例初始化模塊中,我們可看到代碼不能作為類初始化模塊(即if語句)的一部分執行。所以實際上,一個實例初始化模塊就是一個匿名內部類的構建器。當然,它的功能是有限的;我們不能對實例初始化模塊進行過載處理,所以只能擁有這些構建器的其中一個。