当前位置: 首页 > 面试题库 >

动态构建匿名类混乱

邵奇
2023-03-14
问题内容

我正在尝试使用反射使匿名类的实例。但是有时我在实例化过程中看到了奇怪的行为。

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

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(我不确定为什么您的堆栈跟踪未在中给出行号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。这意味着对于的类类型,没有no-
arg构造函数object2

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

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

我们看到了

object2类: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是垃圾.NewMain $ 1(java.util.Calendar)

因此,您的匿名类正在寻找Calendar要传递的a。然后,它将为您工作:

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是垃圾邮件.NewMain $ 1(java.lang.Integer,java.util.Calendar)

如果我然后实例化

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

使用调试器进行的内部窥视显示finalInteger为25,而不是最终值30。

事情有些复杂,因为您是在静态环境中执行上述所有操作。如果您将上面的所有代码都移到这样的非静态方法中(请记住,我的类是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引用):

我的ctor是垃圾.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匿名构造函数对此进行了说明:

…匿名构造函数对声明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是垃圾.NewMain $ 1(junk.NewMain,java.lang.String,java.util.Calendar)

ew 我希望这是有道理的,对您有所帮助。我学到了很多,这是肯定的!



 类似资料:
  • 本文向大家介绍详解C# 匿名对象(匿名类型)、var、动态类型 dynamic,包括了详解C# 匿名对象(匿名类型)、var、动态类型 dynamic的使用技巧和注意事项,需要的朋友参考一下 随着C#的发展,该语言内容不断丰富,开发变得更加方便快捷,C# 的锋利尽显无疑。C# 语言从诞生起就是强类型语言,这一性质到今天不曾改变,我想以后也不会变。既然是强类型语言,那编写任一程序均要求满足下面的基本

  • 匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。其语法形式如下: 这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。 匿名类有两种实现方式: 继承一个类,重写其方法。 实现一个接口(可以是多个),实现其方法。 下面通过代码来说明。 程序的输出结果如下: 从输出结果可以看出,匿名内部类有

  • null null 产品版本:NetBeans IDE 7.3.1(构建201306052037)Java:1.7.0_25;Java HotSpot(TM)64位服务器VM 23.25-B01运行时:Java(TM)SE运行时环境1.7.0_25-B17系统:Windows 7 Version6.1在AMD64上运行;CP1252;en_US(nb) 清理、构建和重新启动Netbeans并没有解

  • 问题内容: 我有以下代码片段: 在这里,如何在不创建线程类实例的情况下为线程调用方法? 问题答案: 您已经在创建Thread类的实例-您对此不做任何事情。您甚至 可以不使用局部变量 来 调用: …但是我个人通常会将其分配给局部变量,然后执行您想要执行的其他任何操作(例如,设置名称等),然后启动它:

  • 本文向大家介绍Java匿名类,匿名内部类实例分析,包括了Java匿名类,匿名内部类实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Java匿名类,匿名内部类。分享给大家供大家参考,具体如下: 内部类 匿名类  首发日期 :2018-03-25 内部类: 在一个类中定义另一个类,这样定义的类称为内部类。【包含内部类的类可以称为内部类的外部类】 如果想要通过一个类来使用另一个类,可以定

  • 匿名参数的一个缺陷是你不能轻易让一个方法使用其作为参数或返回值。因为编译器产生的匿名类型,你不能用来作为方法的参数或返回值。这个问题的任何解决方案都是与局限的。你可以使用匿名类型作为泛型类型参数,或者传给参数为 System.Object 的方法。这些都不会觉得特别满意。泛型方法只能假设类型定义在 System.Object 的功能。 System.Object 同样也有局限。当然,有些时候,你会