我正在尝试使用反射创建匿名类的实例。但偶尔我在短时间内看到了奇怪的行为。
请查看这些类似的代码片段
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 :(得分:21)
这是一个非常简单的问题,答案非常复杂。我试着解释一下,请耐心等待。
查看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
没有no-arg构造函数。
首先,object2
是什么类型的?使用代码
System.out.println("object2 class: " + object2.getClass());
我们看到了
object2 class:class 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();
}
给了我们
我的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现在(删除我添加的Integer引用):
我的ctor是junk.NewMain $ 1(junk.NewMain,java.util.Calendar)
Java Language Specification,15.9.3部分以这种方式解释:
如果C是匿名类,则C,S的直接超类是 内在的,那么:
- 如果S是本地类,并且S出现在静态上下文中,那么 参数列表中的参数(如果有)是参数 构造函数,按照它们出现在表达式中的顺序。
- 否则,i的立即封闭的实例 S是构造函数的第一个参数,后跟是 类实例创建的参数列表中的参数 表达式,如果有的话,按照它们出现在表达式中的顺序。
为什么匿名构造函数会接受参数?
由于您无法为匿名内部类创建构造函数,因此实例初始化程序块用于此目的(请记住,您只有该匿名内部类的一个实例)。 VM不知道内部类,因为编译器将所有内容分离为单独的类(例如junk.NewMain $ 1)。该类的ctor包含实例初始化程序的内容。
这由JLS 15.9.5.1 Anonymous Constructors解释:
...匿名构造函数为每个实际有一个形式参数 C的类实例创建表达式的参数 声明。
您的实例初始值设定项引用了Calendar
对象。除了通过构造函数之外,编译器还有什么方法可以将运行时值转换为内部类(仅作为VM的类创建)?
最后(耶),最后一个问题的答案。为什么构造函数不需要String
? JLS 3.10.5的最后一点解释了:
由常量表达式计算的字符串在编译时计算 然后把它们视为文字。
换句话说,您的String
值在编译时是已知的,因为它是一个文字,因此它不需要是匿名构造函数的一部分。为证明这种情况,我们将测试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)
呼。我希望这是有道理的,也很有帮助。我学到了很多东西,这是肯定的!
答案 1 :(得分:4)
因为在第二种情况下,不再有默认构造函数。
在第一种情况下,最后一个字符串被内联,因为它是一个常量。
在第二种情况下,匿名内部类必须接受Calendar的实例到其构造函数中以捕获状态。您可以轻松确认这样做:
Object tmp = object2.getClass().getDeclaredConstructor(Calendar.class).newInstance(calendar);