2011-08-16 69 views
14

我試圖使用反射來創建匿名類的實例。但諷刺的是,我在瞬間看到了奇怪的行爲。匿名類混淆的動態構造

請看看這些代碼相似的片段

public class HideAndSeek { 

    @SuppressWarnings("unchecked") 
    public static void main(String[] args) throws IllegalAccessException, InstantiationException{ 

     final String finalString = "I'm final :)"; 

     Object object2 = new Object(){ 

      { 
       System.out.println("Instance initializing block"); 
       System.out.println(finalString); 
      }   

      private void hiddenMethod() { 
       System.out.println("Use reflection to find me :)"); 
      } 
     }; 

     Object tmp = object2.getClass().newInstance(); 
    } 

} 

此代碼的工作很好,輸出預計

Instance initializing block 
I'm final :) 
Instance initializing block 
I'm final :) 

這個我決定用簡單的方式來改變代碼後(剛添加java.util.Calendar)

import java.util.Calendar; 

    public class HideAndSeek { 

     @SuppressWarnings("unchecked") 
     public static void main(String[] args) throws IllegalAccessException, InstantiationException{ 

      final String finalString = "I'm final :)"; 

      final Calendar calendar = Calendar.getInstance(); 
      System.out.println(calendar.getTime().toString()); //works well 

      Object object2 = new Object(){ 

       { 
        System.out.println("Instance initializing block"); 
        System.out.println(finalString); 

        //simply added this line 
        System.out.println(calendar.getTime().toString()); 
       }   

       private void hiddenMethod() { 
        System.out.println("Use reflection to find me :)"); 
       } 
      }; 

      Object tmp = object2.getClass().newInstance(); 
     } 

    } 

這裏是輸出,我得到了:

Wed Aug 17 02:08:47 EEST 2011 
Instance initializing block 
I'm final :) 
Wed Aug 17 02:08:47 EEST 2011 
Exception in thread "main" java.lang.InstantiationException: HideAndSeek$1 
    at java.lang.Class.newInstance0(Unknown Source) 
    at java.lang.Class.newInstance(Unknown Source) 
    at HideAndSeek.main(HideAndSeek.java:29) 

如您所見 - 新實例尚未創建。

有沒有人可以解釋我,這種變化的原因?

謝謝

+0

如果日曆instnace不是最終的呢? – SJuan76

+0

@ SJuan76。 ...那麼你就不能將它傳遞給匿名的內部類。 –

回答

17

這是一個非常簡單的問題,一個非常複雜的答案。當我試圖解釋它時,請耐心等待。

在那裏異常在Class提出的源代碼展望(我不知道爲什麼你的堆棧跟蹤不Class給行號):

try 
{ 
    Class[] empty = {}; 
    final Constructor<T> c = getConstructor0(empty, Member.DECLARED); 
    // removed some code that was not relevant 
} 
catch (NoSuchMethodException e) 
{ 
    throw new InstantiationException(getName()); 
} 

你看到NoSuchMethodException是被重新排列爲InstantiationException。這意味着對於類型爲object2的類型,沒有無參數構造函數。

首先,什麼類型是object2?隨着代碼

System.out.println("object2 class: " + object2.getClass()); 

我們看到

對象2類:類junk.NewMain $ 1

這是正確的(我在包垃圾運行示例代碼,類NewMain)。

那麼junk.NewMain$1的構造函數是什麼?

Class obj2Class = object2.getClass(); 
try 
{ 
    Constructor[] ctors = obj2Class.getDeclaredConstructors(); 
    for (Constructor cc : ctors) 
    { 
    System.out.println("my ctor is " + cc.toString()); 
    } 
} 
catch (Exception ex) 
{ 
    ex.printStackTrace(); 
} 

這讓我們

我的構造函數是junk.NewMain $ 1(java.util.Calendar中)

所以你的匿名類是尋找一個Calendar中傳遞。這將爲你工作:

Object newObj = ctors[0].newInstance(Calendar.getInstance()); 

如果你有什麼lik E本:

final String finalString = "I'm final :)"; 
final Integer finalInteger = new Integer(30); 
final Calendar calendar = Calendar.getInstance(); 
Object object2 = new Object() 
{ 
    { 
    System.out.println("Instance initializing block"); 
    System.out.println(finalString); 
    System.out.println("My integer is " + finalInteger); 
    System.out.println(calendar.getTime().toString()); 
    } 
    private void hiddenMethod() 
    { 
    System.out.println("Use reflection to find me :)"); 
    } 
}; 

然後我到newInstance通話將無法工作,因爲沒有在構造函數足夠的參數,因爲現在它想:

我的構造函數是junk.NewMain $ 1(java中。 lang.Integer,java.util.Calendar中)

如果我然後實例與

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance()); 

使用調試器內部窺視顯示finalInteger是25而不是最終值30.

事情有點複雜,因爲你在靜態上下文中執行上述所有操作。如果你把上面所有的代碼,並將其移動到一個非靜態方法像這樣(請記住,我的課是junk.NewMain):

public static void main(String[] args) 
{ 
    NewMain nm = new NewMain(); 
    nm.doIt(); 
} 

public void doIt() 
{ 
    final String finalString = "I'm final :)"; 
    // etc etc 
} 

,你會發現構造函數爲您的內部類是現在(除我加入整數參考):

我的構造函數是junk.NewMain $ 1(junk.NewMain,java.util.Calendar中)

Java Language Specification,部分15.9.3這樣解釋道:

如果C是匿名類,和C,S的直接超類,是 內部類,則:

  • 如果S是一個局部類和S中靜態上下文發生時,然後 參數列表中的參數(如果有的話)是參數 的參數,它們按它們在表達式中出現的順序排列。
  • 否則,I的相對於立即封閉實例 S是所述第一參數給構造,接着在類實例創建 表達的參數列表中的 參數,如果有的話,在它們出現的順序表達方式。

爲什麼匿名構造函數根本不帶參數?

由於無法爲匿名內部類創建構造函數,因此實例初始化程序塊實現此目的(請記住,您只有該匿名內部類的一個實例)。虛擬機不知道內部類,因爲編譯器將所有內容分離爲單獨的類(例如junk.NewMain $ 1)。該類的ctor包含實例初始化程序的內容。

這是由JLS 15.9.5.1 Anonymous Constructors explayed:

...匿名構造必須爲每個實際 參數類實例創建表達式其中C是聲明 一個正式的參數。

您的實例初始值設定項引用了Calendar對象。除了通過構造函數之外,編譯器會如何將這個運行時值傳入你的內部類(它只是爲虛擬機創建的類)?

最後(耶),最後燃燒的問題的答案。爲什麼構造函數不需要String? JLS 3.10.5的最後一位解釋說:由常量表達式計算

字符串是在編譯時 計算,然後就好像它們是文字處理。

換言之,您的String值在編譯時已知,因爲它是一個文字,因此它不需要是匿名構造函數的一部分。爲了證明這一點的話,我們將在JLS 3.10.5測試下一個聲明:

字符串在運行時通過串聯計算是新創建的,因此 不同。

正是如此更改代碼:

String str1 = "I'm"; 
String str2 = " final!"; 
final String finalString = str1 + str2 

,你會發現你的構造函數是現在(在非靜態上下文):

我的構造函數是junk.NewMain $ 1( junk.NewMain,java.lang.String,java.util.Calendar)

Phew。我希望這是有道理的,並有幫助。我學到了很多,這是肯定的!

+0

布拉沃!很好的研究。 –

+0

+37(如果我可以):很好的回答! –

+0

@Paul感謝您提供非常豐富的回答:) – stemm

4

因爲在第二種情況下沒有默認的構造函數了。

在第一種情況下,最後的字符串被內聯,因爲它是一個常量。

在第二種情況下,匿名內部類必須接受日曆實例到其構造函數中以捕獲狀態。你可以很容易地確認這樣做:

Object tmp = object2.getClass().getDeclaredConstructor(Calendar.class).newInstance(calendar);