1. 简介
在本教程中,我们将探讨 Java 11 引入的新访问控制机制 —— 嵌套(nests)。这个特性优化了嵌套类之间的私有成员访问方式,使得 JVM 能够更自然地支持 Java 源码中允许的嵌套类私有访问行为。
2. Java 11 之前的做法
2.1. 嵌套类型
Java 支持将类和接口嵌套在其他类或接口中(即 嵌套类)。这些嵌套类型之间可以无限制地访问彼此的私有字段、方法和构造器。
例如:
public class Outer {
public void outerPublic() {
}
private void outerPrivate() {
}
class Inner {
public void innerPublic() {
outerPrivate();
}
}
}
尽管 outerPrivate()
是私有的,但它仍然可以在 Inner
类的 innerPublic()
方法中被调用。
我们可以将一个顶级类及其所有嵌套类统称为一个 nest(嵌套组),其中的类互为 nestmates(嵌套伙伴)。
因此,在上面的例子中,Outer
和 Inner
构成了一个嵌套组,并互为嵌套伙伴。
2.2. 桥接方法(Bridge Method)
然而,JVM 层面原本是不支持嵌套类之间直接访问私有成员的。理想情况下,上述代码应该编译失败。但 Java 编译器为了实现这种访问,会引入一层间接调用。
具体来说,对私有成员的调用会被编译成对该类中由编译器生成的一个包级私有桥接方法的调用,该桥接方法再调用真正的私有方法。
这个过程对开发者是透明的。但这种桥接方法会导致生成的字节码体积略微增大,也容易让开发者和工具产生困惑。
2.3. 使用反射访问
这种间接调用方式也会导致反射调用失败。例如,如果我们在 Inner
类中通过反射调用 outerPrivate()
:
public void innerPublicReflection(Outer ob) throws Exception {
Method method = ob.getClass().getDeclaredMethod("outerPrivate");
method.invoke(ob);
}
会抛出如下异常:
java.lang.IllegalAccessException:
Class com.baeldung.Outer$Inner can not access a member of class com.baeldung.Outer with modifiers "private"
Java 11 正是为了解决这些问题,引入了 Nest Based Access Control。
3. 嵌套访问控制机制
Java 11 将嵌套伙伴的概念引入 JVM,并定义了相关的访问规则,从而简化了编译器的工作。
为此,类文件格式新增了两个属性:
- 一个嵌套成员(通常是顶级类)被指定为 nest host(嵌套宿主),它通过
NestMembers
属性列出所有静态已知的嵌套成员。 - 其他嵌套成员则通过
NestHost
属性指向其嵌套宿主。
✅ 因此,类 C
和 D
若为嵌套伙伴,必须拥有相同的嵌套宿主。
- 如果类
C
的NestHost
属性指向类D
,表示C
声称自己是D
所托管的嵌套组成员; - 如果
D
的NestMembers
属性中也包含C
,则验证通过; - 同时,
D
本身也隐式地是其托管嵌套组的成员。
⚠️ 现在不再需要编译器生成桥接方法。
此外,这种机制也消除了反射调用中的异常行为。因此,前面提到的 innerPublicReflection()
方法现在可以正常执行,不会抛出异常。
4. 嵌套伙伴反射 API
Java 11 提供了新的反射 API,用于查询类文件中的嵌套属性。java.lang.Class
类新增了以下三个方法:
4.1. getNestHost()
返回该类所属嵌套组的宿主类:
@Test
public void whenGetNestHostFromOuter_thenGetNestHost() {
assertEquals(NEST_HOST_NAME, Outer.class.getNestHost().getName());
}
@Test
public void whenGetNestHostFromInner_thenGetNestHost() {
assertEquals(NEST_HOST_NAME, Outer.Inner.class.getNestHost().getName());
}
Outer
和 Inner
类都属于嵌套宿主 com.baeldung.Outer
。
4.2. isNestmateOf()
判断给定的类是否为当前类的嵌套伙伴:
@Test
public void whenCheckNestmatesForNestedClasses_thenGetTrue() {
assertTrue(Outer.Inner.class.isNestmateOf(Outer.class));
}
4.3. getNestMembers()
返回该类所属嵌套组的所有成员:
@Test
public void whenGetNestMembersForNestedClasses_thenGetAllNestedClasses() {
Set<String> nestMembers = Arrays.stream(Outer.Inner.class.getNestMembers())
.map(Class::getName)
.collect(Collectors.toSet());
is(nestMembers.size()).equals(2);
assertTrue(nestMembers.contains("com.baeldung.Outer"));
assertTrue(nestMembers.contains("com.baeldung.Outer$Inner"));
}
5. 编译细节对比
5.1. Java 11 之前的桥接方法
我们可以通过反编译类文件查看编译器生成的桥接方法:
$ javap -c Outer
Compiled from "Outer.java"
public class com.baeldung.Outer {
public com.baeldung.Outer();
Code:
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: return
public void outerPublic();
Code:
0: return
static void access$000(com.baeldung.Outer);
Code:
0: aload_0
1: invokespecial #1 // Method outerPrivate:()V
4: return
}
除了构造器和公共方法外,注意 access$000()
方法,这是编译器生成的桥接方法。
而 Inner
类中的 innerPublic()
方法则是通过调用该桥接方法访问私有方法:
$ javap -c Outer\$Inner
Compiled from "Outer.java"
class com.baeldung.Outer$Inner {
final com.baeldung.Outer this$0;
com.baeldung.Outer$Inner(com.baeldung.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/baeldung/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
public void innerPublic();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/baeldung/Outer;
4: invokestatic #3 // Method com/baeldung/Outer.access$000:(Lcom/baeldung/Outer;)V
7: return
}
注意第 19 行注释,innerPublic()
调用了桥接方法 access$000()
。
5.2. Java 11 中的嵌套机制
Java 11 编译器生成的 Outer
类不再包含桥接方法:
$ javap -c Outer
Compiled from "Outer.java"
public class com.baeldung.Outer {
public com.baeldung.Outer();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void outerPublic();
Code:
0: return
}
而 Inner
类现在可以直接调用 outerPrivate()
方法:
$ javap -c Outer\$Inner.class
Compiled from "Outer.java"
class com.baeldung.Outer$Inner {
final com.baeldung.Outer this$0;
com.baeldung.Outer$Inner(com.baeldung.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/baeldung/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
public void innerPublic();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/baeldung/Outer;
4: invokevirtual #3 // Method com/baeldung/Outer.outerPrivate:()V
7: return
}
✅ 可以看到,不再调用桥接方法,而是直接调用私有方法 outerPrivate()
。
6. 总结
本文介绍了 Java 11 引入的基于嵌套组的访问控制机制。该机制使 JVM 原生支持嵌套类之间的私有访问,无需编译器生成桥接方法,同时也消除了反射调用中的不一致行为。
相关代码示例可在 GitHub 获取。