【问题标题】:Java: How do i determine whether a local class defined in an initializer block requires an enclosing instance for instantiation?Java:如何确定在初始化程序块中定义的本地类是否需要封闭实例进行实例化?
【发布时间】:2016-06-05 02:42:27
【问题描述】:

现在我正在实现一个方法,它有一个 Class 类型的参数,如果给定的类对象需要它的封闭类的实例来实例化,则此方法返回一个布尔值。

此方法目前的工作方式如下:

    if (clazz.getEnclosingClass() == null) {
        return false;
    }
    if (clazz.isAnonymousClass() || clazz.isMemberClass()) {
        return !Modifier.isStatic(clazz.getModifiers());
    }
    if (clazz.getEnclosingConstructor() != null) {
        return true;
    }
    final Method enclosingMethod = clazz.getEnclosingMethod();
    if (enclosingMethod != null) {
        return !Modifier.isStatic(enclosingMethod.getModifiers());
    }

解释为什么这样设计:

  1. 它首先检查它是否是顶级类,如果是,算法可以安全地返回 false
  2. 如果类是匿名类或成员类,则如果它不是静态的,则需要关闭实例(如果在静态构造函数/方法/初始化程序块中声明任何类,则它自动为静态)
  3. 现在可以假定该类是本地类(不考虑数组和原语),因此它在构造函数、方法或初始化程序中定义。但是,与匿名类不同的是,本地类从不被视为静态类,但如果本地类定义在非静态块中,则仍需要封闭实例。
  4. 构造函数永远不是静态的,所以在这种情况下返回 true
  5. 如果在方法中定义,如果方法不是静态的,则返回 true

我需要第 6 步来确定本地类是驻留在静态初始化程序块还是实例初始化程序块中,所以我完成了这个函数的实现。

所以这就是反射 API 有点不足的地方。反射包中没有Class.getEnclosingInitializer()之类的方法,也没有代表初始化器的类。

初始化块不是类的成员吗?在 java 1.8 规范中,Member 接口只有实现类 Field、Executable(带有子类 Constructor 和 Method),然后是 MemberName,它超出了大多数反射用户的范围。

我不确定规范背后的人是否忘记了这种情况,如果在静态方法/初始化程序(如匿名类)中声明,本地类实际上应该是静态的。但在我看来,从这个角度来看,它缺乏最后一点一致性。

那么有没有人知道如何确定本地类是在哪种类型的初始化程序块中声明的?

我并不热衷于挖掘字段以寻找与其封闭类相等的类型的合成类型,或者循环遍历它的构造函数以获得类似的东西(旁注:来自Constructor.getParameters() 的参数对象总是在 @ 上返回 false 987654324@ 和 isSynthetic() 无论我尝试什么......这似乎是错误的)。所以,如果我能避免这样的解决方案,那就太好了。

【问题讨论】:

  • 看起来更像是讨论而不是问题。
  • 嗯,我需要一种方法来确定 X,而使用方法 Y 是我希望避免的,所以我的问题是“什么是方法 Z 来确定我不知道的 X?”。

标签: java class reflection static


【解决方案1】:

一个有趣的难题,但恐怕它没有满足您要求的解决方案。一旦源文件被编译成字节码,关于类封闭范围的细节就会丢失。请记住,JVM 不仅适用于 Java 语言,而所描述的问题大多是特定于语言的。


我需要第 6 步来确定本地类是否位于 静态初始化块或实例初始化块

在运行时没有此类信息可用。类文件具有本地或匿名类 (JVMS §4.7.7) 的 EnclosingMethod 属性。但是,没有办法区分封闭实例初始化程序和静态初始化程序。规范明确指出

«如果当前类立即被实例初始化器、静态初始化器、实例变量初始化器或类变量初始化器封闭在源代码中,则方法索引必须为零»

考虑这两种情况:

class Outer {
    {
        // Local class inside instance initializer
        class Local {}
    }
}

对比

class Outer {
    static {
        // Local class inside static initializer
        class Local {
            final Outer this$0;

            Local(Outer outer) {
                this$0 = outer;
            }
        }
    }
}

它们被编译成几乎相同的类文件。唯一的区别是字段this$0 在第一种情况下有SYNTHETIC 标志,但在第二种情况下没有。但是检查这个标志正是你想要避免的。 (为什么?)


如果在静态中声明,本地类实际上应该是静态的 方法/初始化器(如匿名类)

我认为本地类和匿名类都不应该是静态的。此外,Java 语言规范要求它们是非静态的:

  • 修饰符 static 仅适用于成员类,不适用于顶级或本地或匿名类 (JLS §8.1.1)。
  • 匿名类始终是内部类;它永远不是静态的 (JLS §15.9.5)。

因此,getModifiers() 有时会为匿名类返回 STATIC,这显然违反了规范。有一个错误 JDK-8034044 已在即将推出的 JDK 9 中修复。这会破坏算法的第 2 步。


好的,那该怎么办?这取决于您的实际意思

如果给定的类对象需要它的封闭类的实例 实例化它

我想说,上面的定义意味着一个类的所有构造函数都有一个封闭类类型的额外参数。在这种情况下,如果没有封闭类的实例,确实无法实例化一个类。

但是,如果您真的想区分初始化程序和静态初始化程序,您唯一的机会(如我上面所示)是查找带有 SYNTHETIC 标志的字段。

【讨论】:

  • 关于不想使用合成字段进行检查:我不确定使用这种方法的可靠性。但到目前为止,我自己的方法似乎并不可靠,因为我是根据结果而不是规范做出假设,导致我依赖于将在未来的 java 实现中修复的错误。感谢您的详细解释和您投入研究的时间。
【解决方案2】:

初始化程序块不存在于已编译的类文件中。静态初始化块中的代码被放入名为 <clinit> 的静态方法中,而非静态初始化块中的代码被放入类的每个构造函数中。因此,对于在非静态初始化块中定义的本地类,getEnclosingConstructor() 应该返回一个非空值。

【讨论】:

  • 但在我的情况下它返回 null。我的IDE可能有问题吗?我正在使用 netbeans 8.1
  • 实例初始化块不属于语言级别的任何构造函数。考虑一个有多个构造函数的类——getEnclosingConstructor() 会返回哪一个?
猜你喜欢
  • 2017-07-06
  • 1970-01-01
  • 2014-09-14
  • 1970-01-01
  • 1970-01-01
  • 2020-03-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-31
相关资源
最近更新 更多