动态构建匿名类混淆

ste*_*emm 16 java reflection calendar anonymous-class

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

此代码运行良好,并且预期输出

Instance initializing block
I'm final :)
Instance initializing block
I'm final :)
Run Code Online (Sandbox Code Playgroud)

在此之后,我决定以简单的方式更改代码(只是添加了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();
        }

    }
Run Code Online (Sandbox Code Playgroud)

这是我得到的输出:

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)
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢

Pau*_*aul 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());
}
Run Code Online (Sandbox Code Playgroud)

你看到它NoSuchMethodException正在被重新抛出InstantiationException.这意味着类类型没有no-arg构造函数object2.

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

System.out.println("object2 class: " + object2.getClass());
Run Code Online (Sandbox Code Playgroud)

我们看到了

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();
}
Run Code Online (Sandbox Code Playgroud)

这给了我们

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

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

Object newObj = ctors[0].newInstance(Calendar.getInstance());
Run Code Online (Sandbox Code Playgroud)

如果您有这样的事情:

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 :)");
  }
};
Run Code Online (Sandbox Code Playgroud)

然后我的呼吁newInstance将无法工作,因为在ctor中没有足够的参数,因为现在它想要:

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

如果我然后用它实例化

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance());
Run Code Online (Sandbox Code Playgroud)

使用调试器查看内部显示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
}
Run Code Online (Sandbox Code Playgroud)

你会发现你的内部类的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的类实例创建表达式的每个实际参数都有一个形式参数.

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

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

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

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

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

这样改变你的代码:

String str1 = "I'm";
String str2 = " final!";
final String finalString = str1 + str2
Run Code Online (Sandbox Code Playgroud)

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

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

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