2011-08-16 70 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);