JEP 181不兼容性:处理嵌套类


JEP 181是一个基于嵌套的访问控制,引入于Java 11,并且它故意引入了与以前版本的不兼容性。这是一个很好的例子,说明了与以前版本的Java兼容不是一成不变的规则,而是保持语言的一致性和稳定发展。在本文中,我将通过几年前遇到的一个例子来看看这种变化,以及在这种特殊情况下,Java 11是如何使生活变得更加轻松和一致的。

Java向后兼容性仅限于特性,而不是行为。

在Java 11之前

几年前,我写了一篇关于面向Java解释器的ScriptBasic以及如何用Java方法扩展它们的文章。我想让它们像用BASIC编写的一样可用,所以我创建了单元测试。单元测试类包含内部类,这些类有一个可用于BASIC代码的方法。内部类是静态的和私有的,因为除了测试之外,它与其他类没有任何关系;但是,测试代码仍然可以访问类和方法,因为它们位于同一个类中。令我沮丧的是,这些方法无法通过BASIC程序访问。当我试图通过BASIC解释器调用这些方法时,我得到了IllegalAccessException

为了纠正这种情况,经过几个小时的调试和学习,我创建了以下简单代码:

package javax0;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflThrow {
    private class Nested {
        private void m(){
            System.out.println("m called");
        }
    }
    public static void main(String[] args)
            throws NoSuchMethodException,
            InvocationTargetException,
            IllegalAccessException {
        ReflThrow me = new ReflThrow();
        Nested n = me.new Nested();
        n.m();
        Method m = Nested.class.getDeclaredMethod("m");
        m.invoke(n);
    }
}


如果您用Java运行这段代码,其中N < 11,那么您会得到类似的结果:

m called
Exception in thread "main" java.lang.IllegalAccessException: class ReflThrow cannot access a member of class ReflThrow$Nested with modifiers "private"
    at java.base/jdk.internal.reflect.Reflection.throwIllegalAccessException(Reflection.java:423)
    at java.base/jdk.internal.reflect.Reflection.throwIllegalAccessException(Reflection.java:414)
...


但是,它使用Java 11工作正常(并且可以推测,它在Java的更高版本中也能正常工作)。

说明

在JVM的第11版之前,它不处理内部和嵌套类。JVM中的所有类都是顶级类。Java编译器从内部类和嵌套类创建了一个特殊命名的顶级类。例如,一个Java编译器可能会创建类文件ReflThrow.classReflThrow$Nested.class,但是因为它们是JVM的顶级类,所以类中的代码ReflThrow无法调用私有方法m()Nested当有两个不同的顶级类时。

然而,在Java级别上,当这些类从嵌套结构中创建时,这是可能的。为了实现这一点,编译器在类内部创建了一个额外的合成方法Nested代码在ReflThrow可以调用,而这个方法,已经在里面了Nested电话m()

合成方法有改性剂SYNTHETIC以便编译器以后知道其他代码不应该“看到”这些方法。这样,调用方法m()运作良好。
另一方面,当我们尝试调用方法时m()使用它的名称和反射访问,路由直接通过类边界,而不调用任何合成方法,并且因为该方法对于它所在的类是私有的,所以调用抛出异常。

Java 11改变了这一点。已经发布的Java 11中包含的JEP 181引入了嵌套的概念。嵌套允许逻辑上属于同一个代码实体但被编译成不同类文件的类访问彼此的私有成员,而不需要编译器插入可访问性,从而扩展了桥接方法这仅仅意味着有类中有巢,也有属于巢的类。当代码从Java生成时,顶层类是嵌套类,而内部的类是嵌套的。JVM级的这种结构为不同的语言结构留下了很大的空间,并且没有将Java结构的octroi放在执行环境中。JVM的目标是多语种的,并且随着未来GraalVM的引入,它将变得更加“多语种”。使用这种结构的JVM只需看到两个类在同一个嵌套中,因此它们可以相互访问private方法、字段和其他成员。这也意味着不存在具有不同访问限制的桥方法,这样,反射就像普通的Java调用一样通过了完全相同的访问边界。

外卖

Java不会在一夜之间改变,并且大部分是向后兼容的。然而,向后兼容性仅限于特性,而不是行为。JEP 181没有,而且它也从来没有真正打算重现不绝对完美IllegalAccessException对嵌套类的反射访问的抛出行为。这种行为更像是一种实现行为/错误,而不是一种语言特性,在Java 11中已经修复了。