在更高版本的 Java 中,内部类如何访问封闭类的私有成员?

Mru*_*ole 3 java jvm bytecode javac .class-file

我一直在尝试理解java中嵌套类的机制。

考虑以下代码:

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);
    

}
Run Code Online (Sandbox Code Playgroud)

}

我知道内部类是编译器的一种现象,虚拟机不知道它们。内部类被转换为常规类文件,并使用 $ 分隔外部类和内部类名称。

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

我得到以下结果:OuterClass:

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$000(staticNestedClasses.OuterClass);
  static java.lang.String access$100();
  static {};
}
Run Code Online (Sandbox Code Playgroud)

内部类:

class staticNestedClasses.OuterClass$InnerClass {
  private java.lang.String privateInnerField;
  final staticNestedClasses.OuterClass this$0;
  staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
  public void accessMembers();
}
Run Code Online (Sandbox Code Playgroud)

这里我们可以看到编译器通过构造函数将外部类的引用传递给内部类,以便它可以访问外部类的字段和方法:

staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
Run Code Online (Sandbox Code Playgroud)

这个外部类引用存储在final staticNestedClasses.OuterClass this$0

但是 OuterClass$InnerClass 类不能通过外部类引用直接访问私有成员,因此每当编译器检测到内部类对私有成员的访问时,它就会在外部类中生成访问器方法(或 getter 方法)。

在外部类的反汇编文件中我们可以看到编译器生成了这些访问器方法。

static java.lang.String access$000(staticNestedClasses.OuterClass);
static java.lang.String access$100();
Run Code Online (Sandbox Code Playgroud)

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

外层类:

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 {};
}
Run Code Online (Sandbox Code Playgroud)

外部类$内部类:

class staticNestedClasses.OuterClass$InnerClass {
  private java.lang.String privateInnerField;
  private static java.lang.String innerClassStaticField;
  final staticNestedClasses.OuterClass this$0;
  staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
  public void accessMembers();
  static {};
}
Run Code Online (Sandbox Code Playgroud)

这里编译器没有生成任何访问器方法,但代码运行良好。

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

Hol*_*ger 5

阻止一个类访问另一个类\xe2\x80\x99sprivate成员的唯一因素是 JVM(或者确切地说是它的验证器)拒绝访问。因此,要使之成为可能,只需 JVM 的协作即可。

\n

虽然 Java\xc2\xa01.1 以不需要改变 JVM 的方式引入内部类,但 JVM 在此期间经历了如此多的变化,令人惊讶的是,直到 Java\xc2\xa011 才引入了内部类。改变这一点。

\n

Java\xc2\xa011 引入了NestHostNestMembers字节码属性,以允许类文件表示它们属于所谓的 \xe2\x80\x9cnest\xe2\x80\x9d。属于同一嵌套的所有类都可以访问彼此的private成员。如前所述,唯一需要更改的是 JVM\xe2\x80\x99s 验证程序以允许此类访问。当然,编译器也利用了这个功能。另请参见JEP\xc2\xa0181

\n

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

\n

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

\n