JAVA11新特性:Nest-Based Access Control

发布时间:

最后更新:

关键词: JAVA11 内部类 嵌套类

JAVA11里有一个新特性,叫做 (JEP181) Nest-Based Access Control(基于嵌套的访问控制)。该 JEP 将是对 Java 平台的一次技术提升,新特性与 Java 的嵌套类息息相关,嵌套类也时常被不严格地称为“内部类”,因为内部类是嵌套类的唯一可能类型。

改进的嵌套类访问

说到嵌套其实大家都熟悉,就是在类型(类、接口、枚举)里再定义类型,通常也叫内部类。内部类的一大特点就是与外层的类联系紧密,可以互相访问私有成员,可以看做是访问控制的一种特例。但如果看过JAVA11之前的JVM规范的话就知道里面其实并没有允许这么做,无论是不是内部类都一视同仁,不能访问其它类的私有成员,比如可以试试在JAVA10及以前的JAVA上运行下面的代码:

public class Main {

	private static class Inner {
		private int value;
	}

	public static void main(String... args) {
		Field field = Inner.class.getDeclaredField("value");
		field.set(new Inner(), 123); // 抛出 IllegalAccessException
	}
}

但这与我们平时写的好像不一样,谁都知道可以在内部类中直接访问外部类的字段、方法等,那么平时一直使用的访问内部类私有成员是如何实现的呢?其实是编译器帮你生成了几个辅助方法,比如对Inner.value赋值的话,实际上生成的类是这样的:

public class Main {

	private static class Inner {

		private int value;

		// 自动生成的辅助方法
		static int access$102(Inner inner, int value) {
			inner.value = value;
			return value;
		}
	}

	public static void main(String... args) {
		var inner = new Inner();

		// inner.value = 123 会被转换成这样:
		Inner.access$102(inner, 123);
	}
}

access$102这个方法是package-private访问权限,使得能够在内部类中访问私有的成员(上面的代码省略了内部类的构造方法,实际上用private修饰的类默认的构造方法也是私有的,同样需要生成一个package-private的构造方法)。这样的做法虽然能够达到需求,但是却有很大的毛病:首先它与封装相违背,并且导致直接访问和反射具有了不同的权限,使用反射访问仍然会抛出异常,除非使用setAccessible关闭检查;其次如果访问的成员很多的话,每一个成员都要生成这样的方法,导致编译后的类变大。

另外如果访问时出现异常,其调用栈里会有access$xxx等无用的方法干扰视线。

这个问题终于在JAVA11中被解决掉了,使用JDK11以上编译出来的类中不再有这样的辅助方法,内部类的私有成员可以直接访问了。

JAVA10与JAVA11生成的代码对比 JAVA10与JAVA11生成的代码对比

JAVA11的嵌套模型

为了实现直接访问,JAVA11修改了原有的规范,其中增加一条新的规则:

A field or method R is accessible to a class or interface D if and only if any of the following conditions are true:

  • ...
  • R is private and is declared in a different class or interface C, and C and D, are nestmates

其中提到了一个词:nestmates,nestmates指属于同一个顶层类型的所有类型(类、接口),也包括顶层类型自己,其中顶层类型又称为nest host。举例:

public class TopLevel {

	class Inner {
		class DeepInner {}
	}

	static final class StaticInner {}

	interface InnerInterface {}
}

在上面这个TopLevel类内部定义了4个类型,其中有一个DeepInner嵌套了两层。对于这5个类型(包括TopLevel类)来说,它们互为nestmates,它们的nest host都是TopLevel类。

数组、值类型和void没有内部成员,其nest host是自己,nestmates也只有自己。另外,Lambda表达式对象也被视为顶层类。

JAVA11在Class类里新增了3个方法用于访问nestmates信息:

  • Class.getNestMembers 用来获取所有的nestmates。
  • Class.getNestHost 获取nest host。
  • Class.isNestmateOf 判断该类与给定的类是否是nestmates。

nestmates相关的信息记录在编译后的class文件里,在JAVA11编译的class文件(版本55)里新增了两个属性NestHostNestMembers,其中NestMembers记录在顶层类中,内部类则有一个NestHost属性记录了顶层类型。这些属性可以用javap -v查看,在输出的最下面:

class文件里的Nest信息 class文件里的Nest信息

由于修改了类文件格式,对于一些需要在运行期解析或生成class文件的库来说是不兼容的更新,比如AspectJ,升级时不要忘了把这些库也升到最新版本。

对于旧版本的JAVA编译出的class文件来说,这些属性是不存在的,自然也就无法获取Nest相关的信息,但旧版的class文件也能在新版里运行。经测试,如果使用JAVA11运行旧版本编译的class文件,那么每个类型的nest host就是自己,无论是否嵌套在其它类中。顶层类型的nest member只有自身,对内部类型调用getNestMembers()将抛出一个IncompatibleClassChangeError。

其它的作用

该特性的实现修改了JVMS规范,这个改动还是比较大的,如果仅仅为了省去几个多余的方法未免太过麻烦。实际上该特性属于Valhalla项目的一部分,这个项目的主要目标之一是改进JAVA中的值类型和泛型。

JEP中提到,在未来将要实现的泛型特化(generic specialization)中,每个特化类型(specialized type)可被创建为泛型的一个 Nestmate。JAVA也在一步步填以前的坑,在未来,像List<int>这样的值类型泛型可能会生成特化的类,成为List<T>的一个Nestmate。

另外,这项特性可能会影响“密封类”(sealed classes),仅允许 Nestmate 的子类作为密封类。 密封类和真正私有类型的概念,对于使用 Scala 的开发人员并不陌生,它们可以实现模式匹配等有用的功能。

参考资料:

Java Nestmate 稳步推进:https://www.infoq.cn/article/2018%2F03%2FNestmates

Project Valhalla:https://wiki.openjdk.java.net/display/valhalla

Specification for JEP 181:https://cr.openjdk.java.net/~dlsmith/nestmates.html

评论加载中