java炒冷飯系列11 方法和作用域內的內部類 與 匿名內部類
在方法和作用域內的內部類
到目前為止,讀者所看到的只是內部類的典型用途。通常,如要所讀、寫的程式碼包含了內部類,那麼它們都是“平凡的”內部類,簡單並且容易理解。然而,內部類的語法覆蓋了大量其他的更加難以理解的技術。例如,可以在一個方法裡面或者在任意的作用域內定義內部類。這麼做有兩個理由:
- 如前所示,你實現了某型別的介面,於是可以建立並返回對其的引用。
- 你要解決一個複雜的問題,想建立一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。
在後面的例子中,先前的程式碼將被修改,以用來實現:
- 一個定義在方法中的類
- 一個定義在作用域內的類,此作用域在方法的內部
- 一個實現了介面的匿名類
- 一個匿名類,它擴充套件了有非預設構造器的類
- 一個匿名類,它執行欄位初始化
- 一個匿名類,它通過例項初始化實現構造(匿名類不可能有構造器)
第一個例子展示了在方法的作用域內(而不是在其他類的作用域內)建立一個完整的類。這被稱作區域性內部類:
public class Parcel5 {
public Destination destination(String s){
class PDestination implements Destination{
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() {
return label;
}
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
}
PDestination類是destination()方法的一部分,而不是Parcel5
你可以在同一個子目錄下的任意類中對某個內部類使用類識別符號PDestination,這並不會有命名衝突。
下面的例子展示瞭如何在任意的作用域嵌入一個內部類
public class Parcel6 {
private void internalTracking(boolean b) {
if (b) {
class TrackingSlip {
private String id;
public TrackingSlip(String id) {
this.id = id;
}
String getSlip() {
return id;
}
}
TrackingSlip ts = new TrackingSlip("Slip");
String slip = ts.getSlip();
}
// TrackingSlip ts = new TrackingSlip();
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
}
TrackingSlip類被嵌入在if語句的作用域內,這並不是說該類的建立是有條件的,它其實與別的類一起編譯過了。然而,在定義TrackingSlip的作用域之外,它是不可用的;除此這外,它與普通類一樣。
匿名內部類
下面的例子看起來有點奇怪:
public class Parcel7 {
public Contents contents(){
return new Contents() {
private int i = 11;
@Override
public int value() {
return i;
}
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
contents()方法將返回值的生成與表示這個返回值的類的定義結合在一起!另外,這個類是匿名的,它沒有名字。更糟的是,看起來似乎是你正好建立一個Contents物件。但是然後(在到達語句結束的分號這前)你卻說:”等一等,我想在這裡插入一個類的定義。”
這種奇怪的語法指的是:“他建一個繼承自Contents的匿名類的物件。”通過new表示式返回的引用被自動向上轉型為對Contents的引用。上述匿名內部類的語法是下述形式的簡化形式:
public class Parcel7b {
class MyContents implements Contents{
private int i = 11;
@Override
public int value() {
return i;
}
}
public Contents contents(){
return new MyContents();
}
public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
}
在這個匿名內部類中,使用了預設的構造器來生成Contents。下面的程式碼展示的是,如果你的基類需要一個有引數的構造器,應該怎麼辦:
class Wrapping{
private int i;
public Wrapping(int x){
i = x;
}
public int value(){
return i;
}
}
public class Parcel8 {
public Wrapping wrapping(int x) {
return new Wrapping(x){
public int value(){
return super.value();
}
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
int value = w.value();
System.out.println(value);
}
}
只需簡單地傳遞合適的引數給基類的構造器即可,這裡是將x傳進new Wrapping(x)。儘管Wrapping只是一個具有具體實現的普通類,但它還是被其匯出類當作公共“介面”來使用
在匿名內部類末尾的分號,並不是用來標記此內部類結構的。實際上,它標記的是表示式的結束,只不過這個表示式正巧包含了匿名內部類罷了。因此,這與別的地方使用的分號是一致的。
在匿名類中定義欄位時,還能夠對其執行初始化操作
public class Parcel9 {
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}
如果定義一個匿名內部類,並且希望它使用一個在其外部定義的物件,那麼編譯器會要求其引數引用是final的,就像你在destination()的引數中看到的那樣。如果你忘記了,將會得到一個編譯時錯誤訊息。
如果只是簡單地給一個欄位賦值,那麼此例中的方法是很好的。但是,如果想做一些類似的構造器的行為,該怎麼辦呢?在匿名類中不可能有命名構造器(因為它根本沒名字!),但是通過例項初始化,就能夠達到為匿名內部類建立一個構造器的效果,就像這樣
abstract class Base{
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
@Override
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(48);
base.f();
}
}
在此例中,不要求變數i一定是final的。因為i被傳遞給匿名類的基類的構造器,它並不會在匿名類內部被直接使用。
下例是帶例項初始化的“parcel”形式。注意destination()的引數必須是final的,因為它們是在匿名類內部使用的。
public class Parcel10 {
public Destination destination(final String dest, final float price) {
return new Destination() {
private int cost;
{
cost = Math.round(price);
if (cost > 0) {
System.out.println("Over budget");
}
}
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Tasmania", 101.395F);
}
}
在例項初始化操作的內部,可以看到有一段程式碼,它們不能作為欄位初始化動作的一部分來執行(就是if語句)。所以對於匿名而言,例項初始化的實際效果就是構造器。當然它受到了限制--你不能過載例項初始化方法,所以你僅有一個這樣的構造器。
匿名內部類與正規的繼承相比有些限制,因為匿名內部類即可以擴充套件類,也可以實現介面,但是不能兩者兼備。而且如果是實現介面,也只能實現一個介面。
參考文獻
《Java程式設計思想》10.5方法和作用域內的內部類
《Java程式設計思想》10.6匿名內部類