内部 class 如何访问包含 class' 更高版本 Java 中的私有成员?

How do inner class access enclosing class' private members in higher versions of Java?

我一直在努力理解 java 中嵌套 classes 的机制。

考虑以下代码:

public class OuterClass {

private String privateOuterField = "Private Outer Field";

public static String publicStaticOuterField = "Public static outer field";

private static String privateStaticOuterField = "private static outer field";


class InnerClass{
    private String privateInnerField = "Private Inner Field";
    
    //non-final static data members not allowed in java 1.8 but allowed in java 17.0
    //private static String innerClassStaticField = "Private Inner Class Static Field";   
    
    public void accessMembers() {
        System.out.println(privateOuterField);
        System.out.println(publicStaticOuterField);
    }
}

static class StaticInnerClass{
    
    private String privateStaticInnerField = "Private Inner Field of static class";
    
    public void accessMembers(OuterClass outer) {
        //System.out.println(privateOuterField);  //error
        System.out.println(outer.privateOuterField);
        System.out.println(publicStaticOuterField);
        System.out.println(privateStaticOuterField);
    }
}
    
public static void main(String[] args) {
    
    OuterClass outerObj = new OuterClass();
    OuterClass.InnerClass innerObj = outerObj.new InnerClass();
    
    StaticInnerClass staticInnerObj = new StaticInnerClass();
    
    innerObj.accessMembers();
    staticInnerObj.accessMembers(outerObj);
    

}

}

我知道内部classes是编译器的现象,虚拟机不知道。内部 classes 被翻译成常规 class 文件,其中 $ 分隔外部和内部 class 名称。

为了更详细地了解这种机制,我尝试使用 javap -p 命令反汇编在 java 1.8 版中编译的 class 文件。

我得到了以下结果: 外部类:

public class staticNestedClasses.OuterClass {
  private java.lang.String privateOuterField;
  public static java.lang.String publicStaticOuterField;
  private static java.lang.String privateStaticOuterField;
  public staticNestedClasses.OuterClass();
  public static void main(java.lang.String[]);
  static java.lang.String access[=14=]0(staticNestedClasses.OuterClass);
  static java.lang.String access0();
  static {};
}

内部类:

class staticNestedClasses.OuterClass$InnerClass {
  private java.lang.String privateInnerField;
  final staticNestedClasses.OuterClass this[=15=];
  staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
  public void accessMembers();
}

这里可以看到编译器通过构造函数将outerclass的引用传递给了innerclass,从而可以访问outerclass的字段和方法:

staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);

这个外部 class 引用存储在 final staticNestedClasses.OuterClass this[=20=]

但是 OuterClass$InnerClass class 不能通过外部 class 引用直接访问私有成员,所以每当编译器检测到从内部 class 访问私有成员时,它会生成访问器方法 (或 getter 方法)在外部 class.

在outerclass的反汇编文件中可以看到编译器生成了这些访问器方法

static java.lang.String access[=17=]0(staticNestedClasses.OuterClass);
static java.lang.String access0();

但是当我在 java 17.0 中编译相同的代码并反汇编 class 文件时,我得到了以下结果。

外class:

public class staticNestedClasses.OuterClass {
  private java.lang.String privateOuterField;
  public static java.lang.String publicStaticOuterField;
  private static java.lang.String privateStaticOuterField;
  public staticNestedClasses.OuterClass();
  public static void main(java.lang.String[]);
  static {};
}

外类$内类:

class staticNestedClasses.OuterClass$InnerClass {
  private java.lang.String privateInnerField;
  private static java.lang.String innerClassStaticField;
  final staticNestedClasses.OuterClass this[=19=];
  staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
  public void accessMembers();
  static {};
}

这里编译器没有生成任何访问器方法但是代码工作正常。

那么内部class是如何访问外部class的私有成员的呢?

唯一阻止 class 访问另一个 class 的 private 成员的是 JVM(或确切地说是它的验证程序)拒绝访问。因此,要使其成为可能,只需 JVM 的协作即可实现。

虽然 Java 1.1 以不需要更改 JVM 的方式引入了内部 classes,但同时 JVM 经历了如此多的变化,令人惊讶的是直到 Java 11 才改变它。

Java 11 介绍了 NestHost and NestMembers bytecode attributes to allow class files to denote that they belong to a so called “nest”. All classes belonging to the same nest are allowed to access each others private members. As said, the only thing that needed to be changed, is the JVM’s verifier to allow such access. And, of course, the compiler to utilize this feature. See also JEP 181

所以你可以说 JVM 仍然不知道关于内部 classes 的任何信息,因为哪个 classes 属于一个嵌套,是由生成 [=31] 的工具决定的=] 文件(例如 Java 源代码编译器)。因此,可以使用其他工具使用嵌套生成 class 文件,而无需遵循内部 class 语义。

为了完成,应该提到 class 文件还使用 InnerClasses 属性包含有关内部 class 关系的信息。但这仅供编译器和反射使用,而 JVM 在决定访问是否合法时不使用此信息。