JEP 181是基于嵌套的访问控制https://openjdk.java.net/jeps/181 。 它是在Java 11中引入的,它故意引入了与先前版本的不兼容性。 这是一个很好的例子,与Java的先前版本兼容并不是刻板的规则,而是保持语言的一致性和稳定发展。 在本文中,我将通过几年前遇到的一个例子来研究这种变化,以及在这种特殊情况下Java 11如何使生活更轻松,更一致。
Java向后兼容性仅限于功能而非行为
原始情况
几年前,当我编写可以用Java方法扩展的ScriptBasic for 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(其中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中也可以正常运行)。
说明
直到Java 11版本,JVM都不处理内部和嵌套类。 JVM中的所有类都是顶级类。 Java编译器从内部和嵌套类创建一个特别命名的顶级类。 例如,其中一个Java编译器可以创建类文件ReflThrow.class
和ReflThrow$Nested.class
。 因为它们是JVM的顶级类,所以当ReflThrow
类是两个不同的顶级类时,它们中的代码无法调用Nested
的私有方法m()
。
但是,在Java级别,从嵌套结构创建这些类是可能的。 为了实现这一点,编译器在Nested
类中创建了一个额外的综合方法, ReflThrow
中的代码可以调用该方法,而Nested
已经存在的该方法将调用m()
。
合成方法具有修饰符SYNTHETIC
因此编译器稍后知道其他代码不应“看到”这些方法。 这样,调用方法m()
效果很好。
另一方面,当我们尝试使用名称和反射访问来调用方法m()
,路由将直接通过类边界,而无需调用任何合成方法,并且由于该方法对于它所在的类是私有的,因此调用引发异常。
Java 11对此进行了更改。 结合到已经发布的Java 11中的JEP 181引入了概念嵌套。 “嵌套允许类在逻辑上属于同一代码实体,但被编译成不同的类文件,而无需编译器插入可扩展访问性的桥方法,即可访问彼此的私有成员。” 它仅表示存在作为嵌套的类,并且存在属于嵌套的类。 从Java生成代码时,顶级类是嵌套类,而内部的类是嵌套的。 JVM级别的此结构为不同的语言结构留有很大空间,并且不会在执行环境中出现Java结构的麻烦。 JVM旨在成为多语言的,并且随着将来GraalVM的引入,它甚至将成为“更多”的多语言。 使用这种结构的JVM只是看到两个类在同一个嵌套中,因此它们可以互相访问private
方法,字段和其他成员。 这也意味着没有桥接方法具有不同的访问限制,并且反射与普通的Java调用完全通过相同的访问边界。
摘要/外卖
Java不会一夜之间发生变化,并且大部分是向后兼容的。 但是,向后兼容性仅限于功能而非行为。 JEP181没有,并且它从未真正打算重现对嵌套类的反射访问的并非绝对完美的IllegalAccessException
抛出行为。 此行为是实现行为/错误,而不是语言功能,并且已在Java 11中修复。
翻译自: https://www.javacodegeeks.com/2018/10/jep-181-incompatibility-nesting-classes.html