匿名类混淆的动态构建

2022-01-11 00:00:00 calendar reflection java anonymous-class

我正在尝试使用反射创建匿名类的实例.但我偶尔会在实例化过程中看到奇怪的行为.

I'm trying to make instances of anonymous classes using reflection. But ocassionally I've seen strange behaviour during instantination.

请看这些相似的代码片段

Please, look at these similar fragments of code

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();
    }

}

这段代码运行良好,预期的输出

This code works well, and the output expected

Instance initializing block
I'm final :)
Instance initializing block
I'm final :)

在此之后,我决定以简单的方式更改代码(只是添加了 java.util.Calendar)

After this I've decided to change code in simple way (just added 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)

如您所见 - 尚未创建新实例.

As you may see - new instance hasn't been created.

谁能解释一下,这些变化的原因?

Could anybody explain me, the reason of such changes?

谢谢

推荐答案

这是一个非常简单的问题,但答案却非常复杂.请耐心等待我试图解释它.

This is a very simple question with a very complex answer. Please bear with me as I try to explain it.

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

Looking at the source code where the exception is raised in Class (I'm not sure why your stack trace doesn't give the line numbers in 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 的类类型没有无参数构造函数.

you see that NoSuchMethodException is being rethrown as InstantiationException. This means there is not a no-arg constructor for the class type of object2.

首先,object2是什么类型?用代码

First, what type is object2? With the code

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

我们看到了

object2 类:class junk.NewMain$1

object2 class: class junk.NewMain$1

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

which is correct (I run sample code in package junk, class NewMain).

那么junk.NewMain$1的构造函数是什么?

What then are the constructors of 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)

my ctor is junk.NewMain$1(java.util.Calendar)

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

So your anonymous class is looking for a Calendar to be passed in. This will then work for you:

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 中没有足够的参数,因为现在它想要:

then my call to newInstance won't work because there are not enough arguments in the ctor, because now it wants:

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

my ctor is junk.NewMain$1(java.lang.Integer,java.util.Calendar)

如果我用

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

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

a peek inside using the debugger shows that finalInteger is 25 and not the final value 30.

事情有点复杂,因为您是在静态上下文中执行上述所有操作.如果您将上面的所有代码移到像这样的非静态方法中(请记住,我的类是 junk.NewMain):

Things are slightly complicated because you're doing all of the above in a static context. If you take all your code above and move it into a non-static method like so (remember, my class is 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
}

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

you'll find the ctor for your inner class is now (removing my added Integer reference):

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

my ctor is junk.NewMain$1(junk.NewMain, java.util.Calendar)

Java 语言规范,15.9.3 是这样解释的:

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

If C is an anonymous class, and the direct superclass of C, S, is an inner class, then:

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

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

Why does the anonymous constructor take arguments at all?

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

Since you can't create a constructor for an anonymous inner class, the instance initializer block serves that purpose (remember, you only have one instance of that anonymous inner class). The VM has no knowledge of the inner class as the compiler separates everything out as individual classes (e.g. junk.NewMain$1). The ctor for that class contains the contents of the instance initializer.

这是由 JLS 15.9.5.1匿名构造函数:

...匿名构造函数对每个实际参数都有一个形参类实例创建表达式的参数,其中 C 是声明.

...the anonymous constructor has one formal parameter for each actual argument to the class instance creation expression in which C is declared.

您的实例初始化程序具有对 Calendar 对象的引用.除了通过构造函数之外,编译器如何将运行时值获取到您的内部类(它只是作为 VM 的一个类而创建)?

Your instance initializer has a reference to a Calendar object. How else is the compiler going to get that runtime value into your inner class (which is created as just a class for the VM) except through the constructor?

最后(耶),最后一个亟待解决的问题的答案.为什么构造函数不需要 String?JLS 最后一点 3.10.5 解释说:

Finally (yay), the answer to the last burning question. Why doesn't the constructor require a String? The last bit of JLS 3.10.5 explains that:

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

Strings computed by constant expressions are computed at compile time and then treated as if they were literals.

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

In other words, your String value is known at compile time because it's a literal so it does not need to be part of the anonymous constructor. To prove this is the case we'll test the next statement in JLS 3.10.5:

在运行时通过连接计算的字符串是新创建的,并且因此不同.

Strings computed by concatenation at run time are newly created and therefore distinct.

因此更改您的代码:

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

你会发现你的 ctor 现在是(在非静态上下文中):

and you'll find your ctor is now (in the non-static context):

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

my ctor is junk.NewMain$1(junk.NewMain,java.lang.String,java.util.Calendar)

呸.我希望这是有道理的并且是有帮助的.我学到了很多,这是肯定的!

Phew. I hope this makes sense and was helpful. I learned a lot, that's for sure!

相关文章