《Thanking in Java》10.內部類

內部類是一種非常有用的特性,因為它允許你把一些邏輯相關的類組織在一起,并控制位于內部的類的可視性。

如果想從外部類的非靜態方法之外的任意位置創建某個內部類的對象,那么必須具體地指明這個對象的類型,OutClassName.InnerClassName

內部類自動擁有對其外部類所有成員的訪問權。

內部類的對象只能在與其外圍類的對象相關聯的情況下才能被創建,構建內部類對象時,需要一個指向其外圍類對象的引用,如果編譯器訪問不到這個引用就會報錯。

10.3 使用.this與.new

如果你需要生成對外部類對象的引用,可以使用外部類的名字后面緊跟.this,這樣產生的引用自動地具有正確的類型,這一點在編譯期就被知曉并受到檢查,因此沒有任何運行時開銷。

public class DotThis {
  void f() { System.out.println("DotThis.f()"); }
  public class Inner {
    public DotThis outer() {
      return DotThis.this;
      // A plain "this" would be Inner's "this"
    }
  }
  public Inner inner() { return new Inner(); }
  public static void main(String[] args) {
    DotThis dt = new DotThis();
    DotThis.Inner dti = dt.inner();
    dti.outer().f();
  }
} /* Output:
DotThis.f()
*///:~

創建某個內部類的對象,需要使用.new語法,必須使用外部類的對象來創建該內部類對象。

public class DotNew {
  public class Inner {}
  public static void main(String[] args) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner();
  }
} ///:~

10.4 內部類與向上轉型

內部類——某個接口的實現——能夠完全不可見,并且不可用。所得到的只是指向基類或接口的引用,所以能夠很方便地隱藏實現細節。

private內部類給類的設計者提供了一種途徑,通過這種方式可以完全阻止任何依賴于類型的編碼,并且完全隱藏了實現的細節。此外,從客戶端程序員的角度來看,由于不能訪問任何新增加的、原本不屬于公共接口的方法,所以擴展接口是沒有價值的。

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

可以在一個方法里或者在任意的作用域內定義內部類,這么做有兩個理由:

  1. 你實現了某類型的接口,于是可以創建并返回對其的引用。
  2. 要解決一個復雜的問題,想創建一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。
//: innerclasses/Parcel5.java
// Nesting a class within a method.

public class Parcel5 {
  public Destination destination(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) {
    Parcel5 p = new Parcel5();
    Destination d = p.destination("Tasmania");
  }
} ///:~

在方法的作用域內(而不是在其他類的作用域內)創建一個完整的類,這被稱為局部內部類。在destionation方法之外不能訪問PDestination,在return語句中又向上轉型成基類。

內部類可以在任意的作用域內嵌入,并不是說該類的創建是有條件的,它與其他別的類一起編譯過了,然而在其作用域之外他是不可用的。

10.6 匿名內部類

匿名內部類指的是創建一個繼承自某個基類的匿名類的對象。

在匿名類末尾的分號,并不是用來標記此內部類結束的。它標記的是表達式的結束,只不過這個表達式正巧包含了匿名內部類罷了。

如果匿名內部類使用一個在外部定義的對象,那么編譯器會要求其參數引用是final的。

匿名類中不可能有命名構造器(因為它根本沒有名字),但通過實例初始化,就能夠達到為匿名內部類創建一個構造器的效果。

//: innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import static net.mindview.util.Print.*;

abstract class Base {
  public Base(int i) {
    print("Base constructor, i = " + i);
  }
  public abstract void f();
}   

public class AnonymousConstructor {
  public static Base getBase(int i) {
    return new Base(i) {
      { print("Inside instance initializer"); }
      public void f() {
        print("In anonymous f()");
      }
    };
  }
  public static void main(String[] args) {
    Base base = getBase(47);
    base.f();
  }
} /* Output:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
*///:~

這里的變量i不是final的,因為i被傳遞給匿名類的基類的構造器,它并不會在匿名類內部被直接使用。

匿名內部類與正規的繼承相比有些受限,因為匿名內部類既可以擴展類,也可以實現接口,但是不能兩者兼備。而且如果是實現接口,也只能實現一個接口。

利用匿名內部類可以更好地創建工廠方法:

//: innerclasses/Factories.java
import static net.mindview.util.Print.*;

interface Service {
  void method1();
  void method2();
}

interface ServiceFactory {
  Service getService();
}   

class Implementation1 implements Service {
  private Implementation1() {}
  public void method1() {print("Implementation1 method1");}
  public void method2() {print("Implementation1 method2");}
  public static ServiceFactory factory =
    new ServiceFactory() {
      public Service getService() {
        return new Implementation1();
      }
    };
}   

class Implementation2 implements Service {
  private Implementation2() {}
  public void method1() {print("Implementation2 method1");}
  public void method2() {print("Implementation2 method2");}
  public static ServiceFactory factory =
    new ServiceFactory() {
      public Service getService() {
        return new Implementation2();
      }
    };
}   

public class Factories {
  public static void serviceConsumer(ServiceFactory fact) {
    Service s = fact.getService();
    s.method1();
    s.method2();
  }
  public static void main(String[] args) {
    serviceConsumer(Implementation1.factory);
    // Implementations are completely interchangeable:
    serviceConsumer(Implementation2.factory);
  }
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~

優先使用類而不是接口,如果你的設計中需要某個接口,你必須了解它。否則,不到迫不得已,不要將其放到你的設計中。

10.7 嵌套類

如果不需要內部類對象與其外圍類對象之間有聯系,那么可以將內部類聲明為static,這通常稱為嵌套類。

普通的內部類對象隱式地保存了一個引用,指向創建它的外圍類對象。但是,當內部類是static的,嵌套類意味著:

  1. 要創建嵌套類的對象,并不需要其外圍類的對象。
  2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。

正常情況下不能在接口內部放置任何代碼,但嵌套類可以作為接口的一部分。因為類是static的,只是將嵌套類置于接口的命名空間內,這并不違反接口的原則,甚至可以在內部類實現其外圍接口。

//: innerclasses/ClassInInterface.java
// {main: ClassInInterface$Test}

public interface ClassInInterface {
  void howdy();
  class Test implements ClassInInterface {
    public void howdy() {
      System.out.println("Howdy!");
    }
    public static void main(String[] args) {
      new Test().howdy();
    }
  }
} /* Output:
Howdy!
*///:~

一個內部類被嵌套多少層并不重要——它能透明地訪問所有它所嵌入的外圍類的所有成員。

10.8 為什么需要內部類

每個內部類都能獨立地繼承自一個接口的實現,所以無論外圍類是否已經繼承了某個接口的實現,對于內部類都沒有影響。內部類使得多重繼承的解決方案變得完整。

使用內部類獲得其他一些特性:

  1. 內部類可以有多個實例,每個實例都有自己的狀態信息,并且與其外圍類對象的信息相互獨立。
  2. 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或繼承同一個類。
  3. 創建內部類對象的時刻并不依賴于外圍類對象的創建。
  4. 內部類并沒有令人迷惑的is-a關系,它就是一個獨立的實體。
    上面的地三條,這里的重音在時刻兩個字,作者想突出內部類是外部類“輕量級的可選組件”這個特性。其實也很好理解,比如迭代器作為很多容器的外部類,并不是在創建容器的時候就被一起創建的。而是要我們再需要它的時候,手動創建實例,日工容器內部元素的視圖。突出“optional”的特性,而不是說它本身和外部類容器沒關系。
//: innerclasses/Callbacks.java
// Using inner classes for callbacks
package innerclasses;
import static net.mindview.util.Print.*;

interface Incrementable {
  void increment();
}

// Very simple to just implement the interface:
class Callee1 implements Incrementable {
  private int i = 0;
  public void increment() {
    i++;
    print(i);
  }
}   

class MyIncrement {
  public void increment() { print("Other operation"); }
  static void f(MyIncrement mi) { mi.increment(); }
}   

// If your class must implement increment() in
// some other way, you must use an inner class:
class Callee2 extends MyIncrement {
  private int i = 0;
  public void increment() {
    super.increment();
    i++;
    print(i);
  }
  private class Closure implements Incrementable {
    public void increment() {
      // Specify outer-class method, otherwise
      // you'd get an infinite recursion:
      Callee2.this.increment();
    }
  }
  Incrementable getCallbackReference() {
    return new Closure();
  }
}   

class Caller {
  private Incrementable callbackReference;
  Caller(Incrementable cbh) { callbackReference = cbh; }
  void go() { callbackReference.increment(); }
}

public class Callbacks {
  public static void main(String[] args) {
    Callee1 c1 = new Callee1();
    Callee2 c2 = new Callee2();
    MyIncrement.f(c2);
    Caller caller1 = new Caller(c1);
    Caller caller2 = new Caller(c2.getCallbackReference());
    caller1.go();
    caller1.go();
    caller2.go();
    caller2.go();
  } 
} /* Output:
Other operation
1
1
2
Other operation
2
Other operation
3
*///:~

此實例展示了回調,和當繼承父類重寫父類方法與需要繼承的接口中的方法重疊時,用內部類解決的思路。

內部類在實現控制框架上,可以在總控制類內部實現具體操作的內部類,能夠很好地繼承事件抽象類,而且能夠方便訪問外部類的所有方法和實例。

10.9 內部類的繼承

class WithInner {
  class Inner {}
}

public class InheritInner extends WithInner.Inner {
  //! InheritInner() {} // Won't compile
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
} ///:~

注意構造器的實現。

10.10 內部類可以被覆蓋嗎

//: innerclasses/BigEgg.java
// An inner class cannot be overriden like a method.
import static net.mindview.util.Print.*;

class Egg {
  private Yolk y;
  protected class Yolk {
    public Yolk() { print("Egg.Yolk()"); }
  }
  public Egg() {
    print("New Egg()");
    y = new Yolk();
  }
}   

public class BigEgg extends Egg {
  public class Yolk {
    public Yolk() { print("BigEgg.Yolk()"); }
  }
  public static void main(String[] args) {
    new BigEgg();
  }
} /* Output:
New Egg()
Egg.Yolk()
*///:~

當繼承了某個外圍類的時候,內部類并沒有發生什么特別神奇的變化。這兩個內部類是完全獨立的兩個實體,各自在自己的命名空間內。

//: innerclasses/BigEgg2.java
// Proper inheritance of an inner class.
import static net.mindview.util.Print.*;

class Egg2 {
  protected class Yolk {
    public Yolk() { print("Egg2.Yolk()"); }
    public void f() { print("Egg2.Yolk.f()");}
  }
  private Yolk y = new Yolk();
  public Egg2() { print("New Egg2()"); }
  public void insertYolk(Yolk yy) { y = yy; }
  public void g() { y.f(); }
}   

public class BigEgg2 extends Egg2 {
  public class Yolk extends Egg2.Yolk {
    public Yolk() { print("BigEgg2.Yolk()"); }
    public void f() { print("BigEgg2.Yolk.f()"); }
  }
  public BigEgg2() { insertYolk(new Yolk()); }
  public static void main(String[] args) {
    Egg2 e2 = new BigEgg2();
    e2.g();
  }
} /* Output:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
*///:~

10.11 局部內部類

局部內部類不能有訪問說明符,因為它不是外圍類的一部分;但是它可以訪問當前代碼塊內的常量,以及此外圍類的所有成員。

既然局部內部類的名字在方法外是不可見的,那為什么我們仍然使用局部內部類而不是匿名內部類呢?唯一的理由是,我們需要一個已命名的構造器,或者需要重載構造器,而匿名內部類只能用于實例初始化。另一個理由就是需要不止一個該內部類的對象。

內部類標識符

內部類生成的.class文件以包含它們的Class對象信息。外圍類的名字,加上“$”,再加上內部類的名字。匿名內部類會產生一個數字作為其表示符。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容