动态构建匿名类混淆

我正在尝试使用reflection来创建匿名类的实例。 但偶尔我会在即时通讯中看到奇怪的行为。

请查看这些类似的代码片段

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) 

正如您所看到的 – 尚未创建新实例。

有人可以解释一下,这种变化的原因是什么?

谢谢

这是一个非常简单的问题,答案非常复杂。 我试着解释一下,请耐心等待。

查看在Class引发exception的源代码(我不确定为什么堆栈跟踪不会给出Class的行号):

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

你会看到NoSuchMethodException被重新NoSuchMethodExceptionInstantiationException 。 这意味着object2的类类型没有no-arg构造函数。

首先, object2是什么类型的? 随着代码

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

我们看到了

object2 class:class junk.NewMain $ 1

这是正确的(我在包垃圾,NewMain类中运行示例代码)。

什么是junk.NewMain$1的构造者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(); } 

这给了我们

我的ctor是junk.NewMain $ 1(java.util.Calendar)

因此,您的匿名类正在寻找要传入的Calendar 。这将适用于您:

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

如果您有这样的事情:

 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调用将无法工作,因为ctor中没有足够的参数,因为现在它需要:

我的ctor是junk.NewMain $ 1(java.lang.Integer,java.util.Calendar)

如果我然后用它实例化

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

使用调试器查看内部显示finalInteger为25而不是最终值30。

事情有点复杂,因为你在静态环境中做了以上所有事情。 如果您将上面的所有代码都移到上面并将其移动到非静态方法中(请记住,我的类是垃圾.NewMain):

 public static void main(String[] args) { NewMain nm = new NewMain(); nm.doIt(); } public void doIt() { final String finalString = "I'm final :)"; // etc etc } 

你会发现你的内部类的ctor现在(删除我添加的整数引用):

我的ctor是junk.NewMain $ 1(junk.NewMain,java.util.Calendar)

Java语言规范 ,第15.9.3节以这种方式解释:

如果C是一个匿名类,而C,S的直接超类是一个内部类,那么:

  • 如果S是本地类并且S出现在静态上下文中,则参数列表中的参数(如果有)是构造函数的参数,按它们在表达式中出现的顺序排列。
  • 否则,关于S的i的直接封闭实例是构造函数的第一个参数,后面是类实例创建表达式的参数列表中的参数(如果有),它们按照它们在表达式中出现的顺序。

为什么匿名构造函数会接受参数?

由于您无法为匿名内部类创建构造函数,因此实例初始化程序块可用于此目的(请记住,您只有该匿名内部类的一个实例)。 VM不知道内部类,因为编译器将所有内容分离为单个类(例如junk.NewMain $ 1)。 该类的ctor包含实例初始化程序的内容。

这由JLS 15.9.5.1 Anonymous Constructors解释 :

…匿名构造函数为声明了C的类实例创建表达式的每个实际参数都有一个forms参数。

您的实例初始值设定项具有对Calendar对象的引用。 除了通过构造函数之外,编译器还有什么方法可以将运行时值转换为内部类(仅作为VM的类创建)?

最后(yay),最后一个问题的答案。 为什么构造函数不需要String ? JLS 3.10.5的最后一点解释了:

由常量表达式计算的字符串在编译时计算,然后将其视为文字。

换句话说,您的String值在编译时是已知的,因为它是一个文字,因此它不需要是匿名构造函数的一部分。 为certificate这种情况,我们将测试JLS 3.10.5中的下一个语句:

在运行时通过串联计算的字符串是新创建的,因此是不同的。

这样改变你的代码:

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

你会发现你的ctor现在(在非静态环境中):

我的ctor是junk.NewMain $ 1(junk.NewMain,java.lang.String,java.util.Calendar)

唷。 我希望这是有道理的,也很有帮助。 我学到了很多东西,这是肯定的!

因为在第二种情况下,不再有默认构造函数。

在第一种情况下,最终字符串被内联,因为它是一个常量。

在第二种情况下,匿名内部类必须接受Calendar的实例到其构造函数中以捕获状态。 您可以轻松确认这样做:

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