2012-04-16 59 views
3

我有以下內部類的情況。這個**有多少外部**會擁有這個內部類?

class Outer { 
    private class Inner1 extends InnerBase { 
    } 
    private class Inner2 extends InnerBase { 
    } 
    private class InnerBase { 
    } 
} 

通常我認爲內部類有一個額外的,隱藏「這個」外部類。

但是,會發生什麼是內部類是從其他內部類派生?

所有內部類(Inner1,Inner2,InnerBase)應該有一個額外的這個。

請問Inner1,Inner2有自己的引用到外面嗎? 或者只是重用InnerBase中的一個,導致稍微不同的行爲?

(隱此==指向外的類的實例的引用)在層次結構中

+1

據我所知,''this''總是指向你所在類的實例。從內部類實例中訪問父類實例是沒有辦法的(除非你創建一個)。我不太確定你的意思是「隱藏這個」*。 – 2012-04-16 19:56:52

+1

@Lattyware非靜態嵌套類的每個實例都有對其創建它的封閉類的實例的引用。這也是爲什麼你不能在靜態方法中實例化非靜態嵌套類型的原因。在這個例子中,你可以通過以下方式訪問這個封閉的實例:'Outer.this' – 2012-04-16 20:01:06

+0

@stefanbachert你能解釋行爲差異在哪裏嗎? – 2012-04-16 20:02:49

回答

2

在內部的每個非靜態內部類將具有其自己的參考this。你可以用javap

$ javap -private Outer.Inner1 
Compiled from "Outer.java" 
class Outer$Inner1 extends Outer$InnerBase{ 
    final Outer this$0; 
    private Outer$Inner1(Outer); 
} 

$ javap -private Outer.InnerBase 
Compiled from "Outer.java" 
class Outer$InnerBase extends java.lang.Object{ 
    final Outer this$0; 
    private Outer$InnerBase(Outer); 
    Outer$InnerBase(Outer, Outer$1); 
} 

確認順便說一句,如果你真的想要,你可以利用一些不起眼的Java語法有不同的值父和子類之間的this$0成員。具體方法如下:你

public class Outer { 
    class InnerBase { 
     Outer innerBaseOuter() { return Outer.this; } 
    } 

    class Inner1 extends InnerBase { 
     public Inner1() {} 
     public Inner1(Outer parentOuter) { 
      parentOuter.super(); // set a different super in InnerBase 
     } 
     Outer innerOuter() { return Outer.this; } 
    } 

    public static void main(String[] args) { 
     Outer outer1 = new Outer(); 
     Outer outer2 = new Outer(); 

     Inner1 a = outer1.new Inner1(); 
     System.out.println("Checking (a.base == a.inner) => "+ 
      (a.innerBaseOuter() == a.innerOuter())); 

     Inner1 b = outer1.new Inner1(outer2); 
     System.out.println("Checking (b.base == b.inner) => "+ 
      (b.innerBaseOuter() == b.innerOuter())); 
    } 
} 

程序運行後得到:

Checking (a.base == a.inner) => true 
Checking (b.base == b.inner) => false 
+0

看起來有趣,但我有點困惑,你能解釋一下這個嗎? ^^ – 2012-04-16 20:07:06

+1

我想知道爲什麼InnerBase有兩個構造函數。然後我發現它:第二個是由編譯器插入的合成構造函數。私有類的默認構造函數是私有的,但是任何Inner1構造函數仍然必須調用InnerBase的非私有構造函數。所以一個合成的構造函數被添加了默認的可見性。簽名衝突(兩個構造函數都只需要外部引用)通過添加另一個合成類型Outer $ 1的參數來解決。 – 2012-04-16 20:11:38

+0

'javap -private'輸出已編譯類文件的所有成員。編譯內部類時,編譯器會自動添加一個名爲'this $ 0'的內部字段來保存包含類的「this」引用。如果你仔細想一想,這並不是真正的魔力 - 運行時系統需要存儲一個對包含類實例的引用,而自動生成的成員是他們如何去做的。 – 2012-04-16 20:11:48

0

有一個在你的榜樣「innering」的只有一個級別,所以每個內部類將有存取權限的Outer類爲您提。

下面是有趣的例子:

public class Test { 

    public static void main(String[] args) { 
     Level1 l1 = new Level1(); 
     Level1.Level2 l2 = l1.new Level2(); 
     Level1.Level2.Level3 l3 = l2.new Level3(); 
    } 

    public static class Level1 { 

     private String s = "Level1"; 

     public Level1() { 
      System.out.println(this + ": " + s); 
     } 

     public class Level2 { 

      private String s = "Level2"; 

      public Level2() { 
       System.out.println(this + ": " + s); 
       System.out.println("Level1: " + Level1.this); 
      } 

      public class Level3 extends OtherLevel { 

       private String s = "Level3"; 

       public Level3() { 
        System.out.println(this + ": " + s); 
        System.out.println("Level1: " + Level1.this); 
        System.out.println("Level2: " + Level2.this); 

        System.out.println("super: " + super.toString()); 
       } 
      } 
     } 

     public class OtherLevel { 

      private String s = "OtherLevel"; 

      public OtherLevel() { 
       System.out.println(this + ": " + s); 
      } 
     } 
    } 
} 

輸出:

[email protected]: Level1 
[email protected]: Level2 
Level1: [email protected] 
[email protected]: OtherLevel 
[email protected]: Level3 
Level1: [email protected] 
Level2: [email protected] 
super: [email protected] 
1

每個則要外的相同的實例的引用。他們每個人都有他們自己對Outer的引用,但是這個引用將指向Outer的同一個實例。由於引用無法更改,所以引用外部引用可能是等價的。

class Outer { 
    private class Inner1 extends InnerBase { 
     Outer getOuter() { 
      return Outer.this; 
     } 
    } 

    private class Inner2 extends InnerBase { 
     Outer getOuter() { 
      return Outer.this; 
     } 
    } 

    private class InnerBase { 
     Outer getOuter() { 
      return Outer.this; 
     } 
    } 

    public static void main(String[] args) { 
     new Outer().test(); 
    } 
    public void test() { 
     System.out.println((new Inner1()).getOuter()); 
     System.out.println((new Inner2()).getOuter()); 
     System.out.println((new InnerBase()).getOuter()); 
    } 
} 
1

讓我們來看看該程序:將出現

public class Outer 
{ 
    public Outer() {} 
    class Inner1 extends Outer 
    { 
     public Inner1() 
     { 
      super(); // invokes Object() constructor 
     } 
    } 

    class Inner2 extends Inner1 
    { 
     public Inner2() 
     { 
      super(); // invokes Inner1() constructor 
     } 
    } 
} 

一個錯誤,如果試圖編譯它。

Outer.java:12: cannot reference this before 
supertype constructor has been called 
super(); // invokes Inner1() constructor 

由於Inner2超本身就是一個內部類,一個不起眼的語言 規則發揮作用。如您所知,內部類的實例化(例如 Inner1)需要將封閉實例提供給構造函數。通常情況下, 它是隱式提供,但它也可以與以下形式的類的超類 構造函數調用expression.super(args)

如果外圍實例是隱式供給顯式提供,編譯器生成的表達: 它使用對於該參考超類是其中最內層的類。無可否認,這確實很滿意,但這正是編譯器所做的。在這種情況下,超類是Inner1。因爲當前類, Inner2,間接擴展Outer,它有Inner1作爲繼承成員。因此, 超類構造函數的限定表達式就是這樣。 編譯器提供了一個封閉的實例,重寫this.super。

默認Inner2構造函數在調用超類構造函數之前嘗試引用 this,這是非法的。

蠻力的方式來解決這個問題提供明確合理的 外圍實例:

public class Outer 
{ 
    class Inner1 extends Outer { } 
    class Inner2 extends Inner1 
    { 
     public Inner2() 
     { 
      Outer.this.super(); 
     } 
    } 
} 

這將編譯,但它是複雜的。有一個更好的解決方案: 每當你寫一個成員類,問問自己,這個類真的需要 一個封閉的實例嗎?如果答案是否定的,請將其設爲靜態。內部類有 有時是有用的,但他們可以很容易地引入併發症,使程序 難以理解。它們與泛型,反射和繼承之間存在複雜的相互作用。如果您聲明Inner1是靜態的,則問題會從 開始。如果您還聲明Inner2是靜態的,您實際上可以瞭解該程序執行的操作。

總之,一個類既不是內部類又不是另一個類的子類是合適的。更一般地說,擴展一個內部類很少合適;如果你必須的話,請仔細考慮封閉的事例。大多數成員類可以並應該聲明爲靜態的。