1. 程式人生 > >java炒冷飯系列11 方法和作用域內的內部類 與 匿名內部類

java炒冷飯系列11 方法和作用域內的內部類 與 匿名內部類

在方法和作用域內的內部類

到目前為止,讀者所看到的只是內部類的典型用途。通常,如要所讀、寫的程式碼包含了內部類,那麼它們都是“平凡的”內部類,簡單並且容易理解。然而,內部類的語法覆蓋了大量其他的更加難以理解的技術。例如,可以在一個方法裡面或者在任意的作用域內定義內部類。這麼做有兩個理由:

  1. 如前所示,你實現了某型別的介面,於是可以建立並返回對其的引用。
  2. 你要解決一個複雜的問題,想建立一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。

在後面的例子中,先前的程式碼將被修改,以用來實現:

  1. 一個定義在方法中的類
  2. 一個定義在作用域內的類,此作用域在方法的內部
  3. 一個實現了介面的匿名類
  4. 一個匿名類,它擴充套件了有非預設構造器的類
  5. 一個匿名類,它執行欄位初始化
  6. 一個匿名類,它通過例項初始化實現構造(匿名類不可能有構造器)
    第一個例子展示了在方法的作用域內(而不是在其他類的作用域內)建立一個完整的類。這被稱作區域性內部類:
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

的一部分。所以,在destination()之外不能訪問PDestination。注意出現在return語句中的向上轉型--返回的是Destination的引用,它是PDestination的基類。當然,在destination()中定義了內部類PDestination,並不意味著一旦dest()方法執行完畢,PDestination就不可用了。

你可以在同一個子目錄下的任意類中對某個內部類使用類識別符號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匿名內部類