內部類的用法

第十章 內部類

10.1如何定義內部類

如代碼10.1-1 所示html

public class Parcel1 {
    public class Contents{
        private int value = 0;
    
        public int getValue(){
            return value;
        }
    }
}

這是一個很簡單的內部類定義方式,你能夠直接把一個類至於另外一個類的內部,這種定義Contents類的方式被稱爲內部類java

那麼,就像代碼10.1-1所展現的,程序員該如何訪問Contents中的內容呢?程序員

如代碼10.1-2 所示編程

public class Parcel1 {

    public class Contents{
        private int value = 0;

        public int getValue(){
            return value;
        }
    }

    public Contents contents(){
        return new Contents();
    }

    public static void main(String[] args) {
        Parcel1 p1 = new Parcel1();
        Parcel1.Contents pc1 = p1.contents();
        System.out.println(pc1.getValue());
    }
}

輸出結果: 0ide

就像上面代碼看到的那樣,你能夠寫一個方法來訪問Contents,至關於指向了一個對Contents的引用,能夠用外部類.內部類這種定義方式來建立一個對於內部類的引用,就像Parcel1.Contents pc1 = p1.contents();所展現的,而pc1 至關於持有了對於內部類Contents的訪問權限。ui

如今,我就有一個疑問,若是10.1-2 中的contents方法變爲靜態方法,pc1還能訪問到嗎?this

編譯就過不去,那麼爲何會訪問不到呢?請看接下來的分析。翻譯

10.2 連接到外部類的方式

看到這裏,你還不明白爲何要採用這種方式來編寫代碼,好像只是爲了裝逼?或者你以爲從新定義一個類很麻煩,乾脆直接定義一個內部類得了,好像到如今並無看到這種定義內部類的方式爲咱們帶來的好處。請看下面這個例子10.2-1code

public class Parcel2 {

    private static int i = 11;

    public class Parcel2Inner {

        public Parcel2Inner(){
            i++;
        }

        public int getValue(){
            return i;
        }

    }

    public Parcel2Inner parcel2Inner(){
        return new Parcel2Inner();
    }

    public static void main(String[] args) {
        Parcel2 p2 = new Parcel2();
        for(int i = 0;i < 5;i++){
            p2.parcel2Inner();
        }
        System.out.println("p2.i = " + p2.i);
    }
}

輸出結果: 16htm

當你建立了一個內部類對象的時候,此對象就與它的外圍對象產生了某種聯繫,如上面代碼所示,內部類Parcel2Inner 是能夠訪問到Parcel2中的i的值的,也能夠對這個值進行修改。

那麼,問題來了,如何建立一個內部類的對象呢?程序員不能每次都寫一個方法返回外部類的對象吧?見代碼10.2-2

public class Parcel3 {

    public class Contents {

        public Parcel3 dotThis(){
            return Parcel3.this;
        }

        public String toString(){
            return "Contents";
        }
    }

    public Parcel3 contents(){
        return new Contents().dotThis();
    }

    public String toString(){
        return "Parcel3";
    }

    public static void main(String[] args) {
        Parcel3 pc3 = new Parcel3();
        Contents c = pc3.new Contents();
        Parcel3 parcel3 = pc3.contents();
        System.out.println(pc3);
        System.out.println(c);
        System.out.println(parcel3);
    }
}

輸出:
Parcel3
Contents
Parcel3

如上面代碼所示,Parcel3內定義了一個內部類Contents,內部類中定義了一個方法dotThis(),這個方法的返回值爲外部類的對象,在外部類中有一個contents()方法,這個方法返回的仍是外部類的引用。

10.3 內部類與向上轉型

本文到如今所展現的都是本類持有內部類的訪問權限,那麼,與此類無關的類是如何持有此類內部類的訪問權限呢?並且內部類與向上轉型到底有什麼關係呢?
如圖10.3-1

public interface Animal {

    void eat();
}

public class Parcel4 {

    private class Dog implements Animal {

        @Override
        public void eat() {
            System.out.println("啃骨頭");
        }
    }

    public Animal getDog(){
        return new Dog();
    }

    public static void main(String[] args) {
        Parcel4 p4 = new Parcel4();
        //Animal dog = p4.new Dog();
        Animal dog = p4.getDog();
        dog.eat();
    }
}

輸出: 啃骨頭

這個輸出你們確定都知道了,Dog是由private修飾的,按說非本類的任何一個類都是訪問不到,那麼爲何可以訪問到呢? 仔細想一下便知,由於Parcel4 是public的,而Parcel4是能夠訪問本身的內部類的,那麼Animal也能夠訪問到Parcel4的內部類也就是Dog類,而且Dog類是實現了Animal接口,因此getDog()方法返回的也是Animal類的子類,從而達到了向上轉型的目的,讓代碼更美妙。

10.4 定義在方法中和任意做用域內部的類

上面所展現的一些內部類的定義都是普通內部類的定義,若是我想在一個方法中或者某個做用域內定義一個內部類該如何編寫呢?
你可能會考慮這幾種定義的思路:

  1. 我想定義一個內部類,它實現了某個接口,我定義內部類是爲了返回接口的引用
  2. 我想解決某個問題,而且這個類又不但願它是公共可用的,顧名思義就是封裝起來,不讓別人用
  3. 由於懶...

如下是幾種定義內部類的方式:

  • 一個在方法中定義的類(局部內部類)
  • 一個定義在做用域內的類,這個做用域在方法的內部(成員內部類)
  • 一個實現了接口的匿名類(匿名內部類)
  • 一個匿名類,它擴展了非默認構造器的類
  • 一個匿名類,執行字段初始化操做
  • 一個匿名類,它經過實例初始化實現構造
  • 定義在方法內部的類又被稱爲局部內部類
public class Parcel5 {
        private Destination destination(String s){
    
            class PDestination implements Destination{
    
                String label;
    
                public PDestination(String whereTo){
                    label = whereTo;
                }
    
                @Override
                public String readLabel() {
                    return label;
                }
            }
            return new PDestination(s);
        }
    
        public static void main(String[] args) {
            Parcel5 p5 = new Parcel5();
            Destination destination = p5.destination("China");
            System.out.println(destination.readLabel());
        }
}

輸出 : China

如上面代碼所示,你能夠在編寫一個方法的時候,在方法中插入一個類的定義,而內部類中的屬性是歸類全部的,我在寫這段代碼的時候很好奇,內部類的執行過程是怎樣的,Debugger走了一下發現當執行到p5.destination("China")的時候,先會執行return new PDestination(s),而後纔會走PDestination的初始化操做,這與咱們對其外部類的初始化方式是同樣的,只不過這個方法提供了一個訪問內部類的入口而已。
注: 局部內部類的定義不能有訪問修飾符

  • 一個定義在做用域內的類,這個做用域在方法的內部
public class Parcel6 {
        // 吃椰子的方法
        private void eatCoconut(boolean flag){
            // 若是能夠吃椰子的話
            if(flag){
                class Coconut {
                    private String pipe;
    
                    public Coconut(String pipe){
                        this.pipe = pipe;
                    }
    
                    // 喝椰子汁的方法
                    String drinkCoconutJuice(){
                        System.out.println("喝椰子汁");
                        return pipe;
                    }
                }
                // 提供一個吸管,能夠喝椰子汁
                Coconut coconut = new Coconut("用吸管喝");
                coconut.drinkCoconutJuice();
            }
    
            /**
             * 若是能夠吃椰子的話,你才能夠用吸管喝椰子汁
             * 若是不能接到喝椰子汁的指令的話,那麼你就不能喝椰子汁
             */
            // Coconut coconut = new Coconut("用吸管喝");
            // coconut.drinkCoconutJuice();
        }
    
        public static void main(String[] args) {
            Parcel6 p6 = new Parcel6();
            p6.eatCoconut(true);
        }
}

輸出: 喝椰子汁

如上面代碼所示,只有程序員告訴程序,如今我想吃一個椰子,當程序接收到這條命令的時候,它回答好的,立刻爲您準備一個椰子,並提供一個吸管讓您能夠喝到新鮮的椰子汁。程序員若是不想吃椰子的話,那麼程序就不會爲你準備椰子,更別說讓你喝椰子汁了。

  • 一個實現了匿名接口的類

咱們都知道接口是不能被實例化的,也就是說你不能return 一個接口的對象,你只能是返回這個接口子類的對象,可是若是像下面這樣定義,你會不會表示懷疑呢?

public interface Contents {

    int getValue();
}

public class Parcel7 {

    private Contents contents(){
        return new Contents() {

            private int value = 11;

            @Override
            public int getValue() {
                return value;
            }
        };
    }

    public static void main(String[] args) {
        Parcel7 p7 = new Parcel7();
        System.out.println(p7.contents().getValue());
    }
}

輸出 : 11

爲何可以返回一個接口的定義?並且還有 {},這究竟是什麼鬼? 這實際上是一種匿名內部類的寫法,其實和上面所講的內部類和向上轉型是類似的。也就是說匿名內部類返回的new Contents()其實也是屬於Contents的一個實現類,只不過這個實現類的名字被隱藏掉了,能用以下的代碼示例來進行轉換:

public class Parcel7b {

    private class MyContents implements Contents {

        private int value = 11;

        @Override
        public int getValue() {
            return 11;
        }
    }

    public Contents contents(){
        return new MyContents();
    }

    public static void main(String[] args) {
        Parcel7b parcel7b = new Parcel7b();
        System.out.println(parcel7b.contents().getValue());
    }
}

輸出的結果你應該知道了吧~! 你是否是以爲這段代碼和 10.3 章節所表示的代碼很一致呢?

  • 一個匿名類,它擴展了非默認構造器的類

若是你想返回一個帶有參數的構造器(非默認的構造器),該怎麼表示呢?

public class WithArgsConstructor {

    private int sum;

    public WithArgsConstructor(int sum){
        this.sum = sum;
    }

    public int sumAll(){
        return sum;
    }
}

public class Parcel8 {

    private WithArgsConstructor withArgsConstructor(int x){

        // 返回WithArgsConstructor帶參數的構造器,執行字段初始化
        return new WithArgsConstructor(x){

            // 重寫sumAll方法,實現子類的執行邏輯
            @Override
            public int sumAll(){
                return super.sumAll() * 2;
            }
        };
    }

    public static void main(String[] args) {
        Parcel8 p8 = new Parcel8();
        System.out.println(p8.withArgsConstructor(10).sumAll());
    }
}

以上WithArgsConstructor 中的代碼很簡單,定義一個sum的字段,構造器進行初始化,sumAll方法返回sum的值,Parcel8中的withArgsConstructor方法直接返回x的值,可是在這個時候,你想在返回值上作一些特殊的處理,好比你想定義一個類,重寫sumAll方法,來實現子類的業務邏輯。 Java編程思想198頁中說 代碼中的「;」並非表示內部類結束,而是表達式的結束,只不過這個表達式正巧包含了匿名內部類而已。

  • 一個匿名類,它可以執行字段初始化

上面代碼確實能夠進行初始化操做,不過是經過構造器執行字段的初始化,若是沒有帶參數的構造器,還能執行初始化操做嗎? 這樣也是能夠的。

public class Parcel9 {

    private Destination destination(String dest){
        return new Destination() {

            // 初始化賦值操做
            private String label = dest;

            @Override
            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel9 p9 = new Parcel9();
        System.out.println(p9.destination("pen").readLabel());
    }
}

Java編程思想p198中說若是給字段進行初始化操做,那麼形參必須是final的,若是不是final,編譯器會報錯,這部分提出來質疑,由於我不定義爲final,編譯器也沒有報錯。
我考慮過是否是private的問題,當我把private 改成public,也沒有任何問題。

我不清楚是中文版做者翻譯有問題,仍是通過這麼多Java版本的升級排除了這個問題,我沒有考證原版是怎樣寫的,這裏還但願有知道的大牛幫忙解釋一下這個問題。

  • 一個匿名類,它經過實例初始化實現構造
public abstract class Base {

    public Base(int i){
        System.out.println("Base Constructor = " + i);
    }

    abstract void f();
}

public class AnonymousConstructor {

    private static Base getBase(int i){

        return new Base(i){
            {
                System.out.println("Base Initialization" + i);
            }

            @Override
            public void f(){
                System.out.println("AnonymousConstructor.f()方法被調用了");
            }
        };
    }

    public static void main(String[] args) {
        Base base = getBase(57);
        base.f();
    }
}

輸出:
Base Constructor = 57
Base Initialization 57
AnonymousConstructor.f()方法被調用了

這段代碼和 "一個匿名類,它擴展了非默認構造器的類" 中屬於相同的範疇,都是經過構造器實現初始化的過程。

10.5 嵌套類

10.4 咱們介紹了6種內部類定義的方式,如今咱們來解決一下10.1 提出的疑問,爲何contents()方法變成靜態的,會編譯出錯的緣由:

Java編程思想p201頁講到:若是不須要內部類與其外圍類以前產生關係的話,就把內部類聲明爲static。這一般稱爲嵌套類,也就是說嵌套類的內部類與其外圍類以前不會產生某種聯繫,也就是說內部類雖然定義在外圍類中,可是確實能夠獨立存在的。嵌套類也被稱爲靜態內部類。
靜態內部類意味着:
(1)要建立嵌套類的對象,並不須要其外圍類的對象
(2)不能從嵌套類的對象中訪問非靜態的外圍類對象

代碼示例 10.5-1

public class Parcel10 {

    private int value = 11;

    static int bValue = 12;

    // 靜態內部類
    private static class PContents implements Contents {

        // 編譯報錯,靜態內部類PContents中沒有叫value的字段
        @Override
        public int getValue() {
            return value;
        }

        // 編譯不報錯,靜態內部類PContents能夠訪問靜態屬性bValue
        public int f(){
            return bValue;
        }
    }

    // 普通內部類
    private class PDestination implements Destination {

        @Override
        public String readLabel() {
            return "label";
        }
    }

    // 編譯不報錯,由於靜態方法能夠訪問靜態內部類
    public static Contents contents(){
        return new PContents();
    }

    // 編譯報錯,由於非靜態方法不能訪問靜態內部類
    public Contents contents2(){
        Parcel10 p10 = new Parcel10();
        return p10.new PContents();
    }

    // 編譯不報錯,靜態方法能夠訪問非靜態內部類
    public static Destination destination(){
        Parcel10 p10 = new Parcel10();
        return p10.new PDestination();
    }

    // 編譯不報錯,非靜態方法能夠訪問非靜態內部類
    public Destination destination2(){
        return new PDestination();
    }
}

由上面代碼能夠解釋,10.1編譯出錯的緣由是 靜態方法不能直接訪問非靜態內部類,而須要經過建立外圍類的對象來訪問普通內部類。

10.5.2 接口內部的類

納尼?接口內部只能定義方法,難道接口內部還能放一個類嗎?能夠!
正常狀況下,不能在接口內部放置任何代碼,可是嵌套類做爲接口的一部分,你放在接口中的任何類默認都是public和static的。由於類是static的,只是將嵌套類置於接口的命名空間內,這並不違反接口的規則,你甚至能夠在內部類實現外部類的接口,不過通常咱們不提倡這麼寫

public interface InnerInterface {

    void f();

    class InnerClass implements InnerInterface {

        @Override
        public void f() {
            System.out.println("實現了接口的方法");
        }

        public static void main(String[] args) {
            new InnerClass().f();
        }
    }

    // 不能在接口中使用main方法,你必須把它定義在接口的內部類中
//    public static void main(String[] args) {}
}

輸出: 實現了接口的方法

10.5.3 內部類實現多重繼承

在Java中,類與類之間的關係一般是一對一的,也就是單項繼承原則,那麼在接口中,類與接口之間的關係是一對多的,也就是說一個類能夠實現多個接口,而接口和內部類結合能夠實現"多重繼承",並非說用extends關鍵字來實現,而是接口和內部類的對多重繼承的模擬實現。

參考chenssy的文章 http://www.cnblogs.com/chenssy/p/3389027.html 已經寫的很不錯了。

public class Food {

    private class InnerFruit implements Fruit{
        void meakFruit(){
            System.out.println("種一個水果");
        }
    }

    private class InnerMeat implements Meat{
        void makeMeat(){
            System.out.println("煮一塊肉");
        }
    }

    public Fruit fruit(){
        return new InnerFruit();
    }

    public Meat meat(){
        return new InnerMeat();
    }

    public static void main(String[] args) {
        Food food = new Food();
        InnerFruit innerFruit = (InnerFruit)food.fruit();
        innerFruit.meakFruit();
        InnerMeat innerMeat = (InnerMeat) food.meat();
        innerMeat.makeMeat();
    }
}

輸出:
種一個水果
煮一塊肉

10.6 內部類的繼承

內部類之間也能夠實現繼承,與普通類之間的繼承類似,不過不徹底同樣。

public class BaseClass {

    class BaseInnerClass {

        public void f(){
            System.out.println("BaseInnerClass.f()");
        }
    }

    private void g(){
        System.out.println("BaseClass.g()");
    }
}
/**
 *  能夠看到,InheritInner只是繼承自內部類BaseInnerClass,而不是外圍類
 *  可是默認的構造方式會報編譯錯誤,
 *  必須使用相似enclosingClassReference.super()才能編譯經過
 *  用來來講明內部類與外部類對象引用之間的關聯。
 *
 */
public class InheritInner extends BaseClass.BaseInnerClass{

    // 編譯出錯
//    public InheritInner(){}

    public InheritInner(BaseClass bc){
        bc.super();
    }

    @Override
    public void f() {
        System.out.println("InheritInner.f()");
    }

    /*
    * 加上@Override 會報錯,由於BaseInnerClass 中沒有g()方法
    * 這也是爲何覆寫必定要加上Override註解的緣由,不然默認是本類
    * 中持有的方法,會形成誤解,程序員覺得g()方法是重寫事後的。
    @Override
    public void g(){
        System.out.println("InheritInner.g()");
    }*/

    public static void main(String[] args) {
        BaseClass baseClass = new BaseClass();
        InheritInner inheritInner = new InheritInner(baseClass);
        inheritInner.f();
    }
}

輸出:InheritInner.f()

10.7 內部類的覆蓋

關於內部類的覆蓋先來看一段代碼:

public class Man {

    private ManWithKnowledge man;

    protected class ManWithKnowledge {

        public void haveKnowledge(){
            System.out.println("當今社會是須要知識的");
        }
    }

    // 咱們想讓它輸出子類的haveKnowledge()方法
    public Man(){
        System.out.println("當咱們有了一個孩子,咱們更但願他能夠當一個科學家,而不是網紅");
        new ManWithKnowledge().haveKnowledge();
    }
}

// 網紅
public class InternetCelebrity extends Man {

    protected class ManWithKnowledge {

        public void haveKnowledge(){
            System.out.println("網紅是當今社會的一種病態");
        }
    }

    public static void main(String[] args) {
        new InternetCelebrity();
    }
}

輸出:當咱們有了一個孩子,咱們更但願他能夠當一個科學家,而不是網紅
當今社會是須要知識的

咱們默認內部類是能夠覆蓋的。因此咱們想讓他輸出 InternetCelebrity.haveKnowledge() ,來實現咱們的猜測,可是卻輸出了ManWithKnowledge.haveKnowledge()方法。
這個例子說明當繼承了某個外圍類的時候,內部類並無發生特別神奇的變化,兩個內部類各自獨立,都在各自的命名空間內。

10.8 關於源碼中內部類的表示

因爲每一個類都會產生一個.class 文件,包含了建立該類型對象的所有信息
一樣的,內部類也會生成一個.class 文件
表示方法爲:
OneClass$OneInnerClass

內部類的優勢:一、封裝部分代碼,當你建立一個內部類的時候,該內部類默認持有外部類的引用;二、內部類具備必定的靈活性,不管外圍類是否繼承某個接口的實現,對於內部類都沒有影響;三、內部類可以有效的解決多重繼承的問題。

相關文章
相關標籤/搜索