这里需要考虑多个方面。下面将使用术语“嵌套类”,因为它涵盖了非static(也称为“内部类”)和static 类(source)。
与 private 嵌套类无关,但 JLS §8.2 有一个有趣的 example,它显示了 package-private 或 protected 类中的 public 成员可能有用的地方。
源代码
覆盖方法
当您的嵌套类实现一个接口或扩展一个类并覆盖其方法之一时,则按照JLS §8.4.8.3:
覆盖或隐藏方法的访问修饰符必须至少提供与覆盖或隐藏方法一样多的访问权限
例如:
public class Outer {
private static class Nested implements Iterator<String> {
@Override
public boolean hasNext() {
...
}
@Override
public String next() {
...
}
}
}
覆盖Iterator 方法的hasNext() 和next() 方法必须是public,因为Iterator 方法是公开的。
附带说明:JLS §13.4.7 描述了一个类可以提高其方法之一的访问级别,即使子类覆盖它,也不会导致链接错误。
传达意图
访问限制定义在JLS §6.6.1:
引用类型 [...] 的成员 [...] 仅在类型可访问且声明成员或构造函数允许访问时才可访问
[...]
否则,成员或构造函数被声明为private,并且当且仅当它出现在包含成员或构造函数声明的顶级类型 (§7.6) 的主体内时才允许访问。
因此,private 嵌套类的成员只能从封闭的顶级类型的主体中访问(从源代码的角度来看;另请参见“反射”部分)。有趣的是,“body”还涵盖了其他嵌套类:
public class TopLevel {
private static class Nested1 {
private int i;
}
void doSomething(Nested1 n) {
// Can access private member of nested class
n.i++;
}
private static class Nested2 {
void doSomething(Nested1 n) {
// Can access private member of other nested class
n.i++;
}
}
}
因此,从编译器提供的访问限制的角度来看,在 private 嵌套类中使用 public 成员确实没有意义。
但是,使用不同的访问级别对于传达意图可能很有用,尤其是(正如其他人指出的那样)嵌套类将来可能被重构为单独的顶级类时。考虑这个例子:
public class Cache {
private static class CacheEntry<T> {
private final T value;
private long lastAccessed;
// Signify that enclosing class may use this constructor
public CacheEntry(T value) {
this.value = value;
updateLastAccessed();
}
// Signify that enclosing class must NOT use this method
private void updateLastAccessed() {
lastAccessed = System.nanoTime();
}
// Signify that enclosing class may use this method
public T getValue() {
updateLastAccessed();
return value;
}
}
...
}
编译的类文件
注意 Java 编译器如何处理对嵌套类成员的访问也很有趣。在JEP 181: Nest-Based Access Control(Java 11 中添加)之前,编译器必须创建合成访问器方法,因为类文件无法表达与嵌套类相关的访问控制逻辑。考虑这个例子:
class TopLevel {
private static class Nested {
private int i;
}
void doSomething(Nested n) {
n.i++;
}
}
使用 Java 8 编译并使用 javap -p ./TopLevel$Nested.class 检查时,您会看到添加了合成的 access$008 方法:
class TopLevel$Nested {
private int i;
private TopLevel$Nested();
static int access$008(TopLevel$Nested);
}
这会稍微增加类文件的大小,并可能会降低性能。这就是为什么经常为嵌套类的成员选择包私有(即无访问修饰符)访问以防止创建合成访问方法的原因之一。
对于 JEP 181,这不再是必需的(使用 JDK 11 编译时javap -v 输出):
class TopLevel$Nested
...
{
private int i;
...
private TopLevel$Nested();
...
}
...
NestHost: class TopLevel
...
反射
另一个有趣的方面是反射。遗憾的是,JLS 没有在这方面进行具体验证,但 §15.12.4.3 包含一个有趣的提示:
如果 T 与 D 在不同的包中,并且它们的包在同一个模块中,并且 T 是 public 或 protected,则 T 是可访问的。
[...]
如果 T 是protected,它必然是一个嵌套类型,所以在编译时,它的可访问性会受到包含其声明的类型的可访问性的影响。但是,在链接期间,其可访问性不受包含其声明的类型的可访问性影响。此外,在链接期间,protected T 与 public T 一样可访问。
同样AccessibleObject.setAccessible(...) 根本没有提到封闭类型。确实可以在非public 封闭类型中访问public 或protected 嵌套类型的成员:
test1/TopLevel1.java
package test1;
// package-private
class TopLevel1 {
private static class Nested1_1 {
protected static class Nested1_2 {
public static int i;
}
}
}
test2/TopLevel2.java
package test2;
import java.lang.reflect.Field;
public class TopLevel2 {
public static void main(String... args) throws Exception {
Class<?> nested1_2 = Class.forName("test1.TopLevel1$Nested1_1$Nested1_2");
Field f = nested1_2.getDeclaredField("i");
f.set(null, 1);
}
}
这里的反射能够修改字段test1.TopLevel1.Nested1_1.Nested1_2.i,而不必使其可访问,尽管它位于包私有类中的private嵌套类中。
当您为运行不受信任的代码的环境编写代码时,您应该牢记这一点,以防止恶意代码干扰内部类。
因此,当涉及到嵌套类型的访问级别时,您应该始终选择最不宽松的一种,最好是 private 或 package-private。