Java 匿名类混淆的动态构造

Java 匿名类混淆的动态构造,java,reflection,calendar,anonymous-class,Java,Reflection,Calendar,Anonymous Class,我正在尝试使用反射创建匿名类的实例。但是,偶尔我会在瞬间看到奇怪的行为 请看这些类似的代码片段 public class HideAndSeek { @SuppressWarnings("unchecked") public static void main(String[] args) throws IllegalAccessException, InstantiationException{ final String finalString = "I'm

我正在尝试使用反射创建匿名类的实例。但是,偶尔我会在瞬间看到奇怪的行为

请看这些类似的代码片段

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)

这是我得到的输出:

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)
正如您可能看到的,新实例尚未创建

谁能给我解释一下这些变化的原因吗


谢谢,因为在第二种情况下,没有默认构造函数了

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

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

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

这是一个非常简单的问题,答案非常复杂。请耐心听我解释

查看在
中引发异常的源代码(我不确定为什么堆栈跟踪没有给出
中的行号):

我们看到了

object2类:类垃圾。NewMain$1

这是正确的(我在包junk中运行示例代码,类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)

因此,您的匿名类正在寻找要传入的
日历。这将对您起作用:

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

事情有点复杂,因为您是在静态环境中完成上述所有操作的。如果将上面的所有代码移到非静态方法中(请记住,我的类是junk.NewMain):

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

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

本节对此进行了如下解释:

如果C是匿名类,C的直接超类S是 内部类,然后:

  • 如果S是一个本地类,并且S出现在静态上下文中,那么 参数列表中的参数(如果有)是要 构造函数,按照它们在表达式中出现的顺序
  • 否则,关于 S是构造函数的第一个参数,后跟 类实例创建的参数列表中的参数 表达式(如果有),按它们在表达式中的显示顺序排列
为什么匿名构造函数完全接受参数

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

JLS对此进行了解释:

…匿名构造函数对每个实际值都有一个形式参数 C所在的类实例创建表达式的参数 声明

实例初始值设定项引用了
日历
对象。除了通过构造函数之外,编译器还将如何将该运行时值获取到内部类中(该类仅作为VM的类创建)

最后(耶),最后一个紧迫问题的答案。为什么构造函数不需要
字符串
?JLS的最后一点解释如下:

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

换句话说,您的
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)


呸。我希望这是有意义的,并且是有帮助的。我学到了很多,这是肯定的

如果日历不是最终的呢?@SJuan76。。。那么你就无法将它传递到匿名的内部类中。+37(如果我可以的话):很好的回答!
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 :)");
  }
};
Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance());
public static void main(String[] args)
{
  NewMain nm = new NewMain();
  nm.doIt();
}

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