[Java] 内部类 (inner class) 为何可以访问宿主类的成员 (第二部分)

104 阅读3分钟

Java 内部类 (inner class) 为何可以访问宿主类的成员(第二部分)

背景

[Java] 内部类 (inner class) 为何可以访问宿主类的成员 (第一部分) 中已经提到,内部类 (inner class) 会持有所在宿主类一个实例的引用,所以内部类可以访问对应的宿主类。但是还有一个问题,宿主类和内部类似乎可以互相访问对方的 private 字段/方法,这会违反 java 的 access control 的机制吗?

结论

  1. Java Virtual Machine Specification 中的 5.4.4. Access Control 提到宿主类和内部类能互相访问对方的 private 成员。
  2. 虚拟机通过读取 NestMembers/NestHost 属性,就可以知道宿主类和内部类之间的关系,从而允许它们访问对方的 private 成员。
    • 宿主类的 class 文件中会有 NestMembers 属性
    • 内部类的 class 文件中会有 NestHost 属性

代码

Java Virtual Machine Specification 中的 5.4.4. Access Control有下内容

image.png

nest is a set of classes and interfaces that allow mutual access to their private members. One of the classes or interfaces is the nest host. It enumerates the classes and interfaces which belong to the nest, using the NestMembers attribute (§4.7.29). Each of them in turn designates it as the nest host, using the NestHost attribute (§4.7.28). A class or interface which lacks a NestHost attribute belongs to the nest hosted by itself; if it also lacks a NestMembers attribute, then this nest is a singleton consisting only of the class or interface itself.

从这段文字可以看到,java 语言要求宿主类和内部类能互相访问对方的 private 成员,且相关信息保存在 NestMembers 属性与 NestHost 属性中。

我们用如下代码来进行验证(请将代码保存为 MyOuter4.java)。

public class MyOuter4 {
  private int a;

  // 1. a member class
  class MemberClass {
    private int b = a;
  }
  
  void f() {
    // 2. a local class
    class LocalClass {
      private int c = a;
    }
    // 3. an anonymous class
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        int d = a;
      } 
    };
  }
}

这段代码中有一个宿主类 MyOuter4,以及如下三个内部类(代码中有对应的注释)。 三个内部类都访问了宿主类的 private 字段 a(严谨起见,应该也写点代码让宿主类访问内部类的 private 成员,这里略)。 我们用以下命令对其进行编译。

javac -g -parameters MyOuter4.java

编译后会得到如下 4 个 class 文件。

MyOuter4.class
MyOuter4$1.class
MyOuter4$1LocalClass.class
MyOuter4$MemberClass.class

用以下命令可以查看 MyOuter4.class 中的内容

javap -v -p MyOuter4

完整的内容有点长,读者可以自行查看完整结果。 注意到,结果中确实有 NestMembers 属性,相关内容如下所示。

NestMembers:
  MyOuter4$MemberClass
  MyOuter4$1
  MyOuter4$1LocalClass

用以下命令分别可以查看 MyOuter4$1.classMyOuter4$1LocalClass.classMyOuter4$MemberClass.class 中的内容

javap -v -p 'MyOuter4$1'
javap -v -p 'MyOuter4$1LocalClass'
javap -v -p 'MyOuter4$MemberClass'

它们的结果中都有如下的内容。(也就是 NestHost 属性部分)

NestHost: class MyOuter4

至此,我们已经验证了

  1. 宿主类的 class 文件中会有 NestMembers 属性指明对应的内部类
  2. 内部类的 class 文件中会有 NestHost 属性指明对应的宿主类

至于为何宿主类和内部类都要指定对方,我觉得是出于安全的考虑。 我们以生活中的例子来进行类比。

  1. 某人自称是英国王室某成员的私生子/私生女,但是英国王室从不回应。
  2. 张三声称曾借给李四一些钱,李四否认有借钱的事情。
  3. A自称曾拜B为师,B未承认。

由此可见,如果当事双方只有一方认定某事曾发生过,则此事真伪存疑。所以需要当事双方共同认可。 回到宿主类/内部类的情况,那么就是说宿主类和内部类要互相承认对方。

如果本文对您有帮助,可以继续阅读 [Java] 内部类 (inner class) 为何可以访问宿主类的成员 (第三部分)

参考资料

  1. Java Virtual Machine Specification 中的 5.4.4. Access Control
  2. Java Virtual Machine Specication 中的 4.7.28. The NestHost Attribute
  3. Java Virtual Machine Specication 中的 4.7.29. The NestMembers Attribute