Java 9为何可运行Java 8编译的使用非导出包的代码?
首先还原你的场景细节:
你用Java 8编译器编译了这段代码:
package pack; import sun.util.calendar.CalendarUtils; public class A { public static void main(String[] args) { System.out.println(CalendarUtils.isGregorianLeapYear(2018)); } }
编译时收到内部API的警告提示:
gyan@gyan-pc:~/codes/java$ ~/Documents/softwares/Linux/jdk1.8.0_131/bin/javac -d . a.java a.java:2: warning: CalendarUtils is internal proprietary API and may be removed in a future release import sun.util.calendar.CalendarUtils; ^ a.java:9: warning: CalendarUtils is internal proprietary API and may be removed in a future release System.out.println(CalendarUtils.isGregorianLeapYear(2018)); ^ 2 warnings
你的默认JRE版本为Java 9:
gyan@gyan-pc:~/codes/java$ java -version java version "9.0.1" Java(TM) SE Runtime Environment (build 9.0.1+11) Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)
但使用Java 9运行编译后的代码却无报错,输出了false。你疑惑的点是:Unnamed Module依赖所有平台模块,但仅能使用对应模块导出的包,而java.base并未导出sun.util.calendar,为何还能访问?
这其实是Java 9模块系统为向后兼容特意保留的宽松访问规则,针对的就是Unnamed Module中的代码:
Unnamed Module的特殊兼容权限
你的代码因为没有module-info.java,运行时会被归入Unnamed Module。Java 9的模块系统对这个特殊模块开了“后门”:它允许Unnamed Module读取所有平台模块的所有包——包括那些未被导出的内部包。这是为了让大量依赖内部API的旧代码,能在Java 9及后续版本中先平稳运行,避免直接崩溃。编译与运行时的规则差异
Java 8本身没有模块系统,内部API可以自由访问,所以编译时仅会发出警告;到了Java 9运行时,模块系统虽然启用,但对Unnamed Module的代码放宽了访问检查。不过这种兼容是临时的,Java 11及以后的版本中,很多内部API被完全强封装或移除,这类代码再运行就会直接报错。可验证的权限控制
Java 9提供了--illegal-access参数来控制内部API的访问:默认值为permit,首次访问未导出API会发警告但允许执行;如果设置为deny,就会直接禁止访问。你可以试试这个命令,此时你的代码会抛出IllegalAccessError:java --illegal-access=deny pack.a
总结来说,这是Java模块系统过渡阶段的兼容妥协,但依赖内部API的代码并不推荐继续使用,后续版本会逐步收紧这类访问权限。
内容的提问来源于stack exchange,提问作者my name is GYAN

