动态构建匿名类混淆
我正在尝试使用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
被重新NoSuchMethodException
为InstantiationException
。 这意味着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);