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

3

我一直在尝试理解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);
    

}

我知道内部类是编译器的现象,虚拟机并不知道它们。内部类会被翻译成常规的类文件,并用 $ 符号将外部类和内部类名称分隔开。

为了更详细地了解这个机制,我尝试使用 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 {};
}

内部类:

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

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

staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);

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

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

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

static java.lang.String access$000(staticNestedClasses.OuterClass);
static java.lang.String access$100();

当我在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 {};
}

OuterClass$InnerClass:

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 {};
}

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

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

1个回答

5
唯一阻止一个类访问另一个类的私有成员的是JVM(或者更精确地说是它的校验器)拒绝访问。所以只要JVM合作,就可以让这个过程变得可能。虽然Java 1.1引入了内部类,而且不需要对JVM进行修改,但JVM已经经历了很多变化,因此直到Java 11才改变这种情况,这相当令人惊讶。
Java 11引入了NestHostNestMembers字节码属性,允许类文件表示它们属于所谓的“巢”。所有属于同一巢的类都可以访问彼此的private成员。正如所说,唯一需要改变的是JVM的验证器,以允许这种访问。当然,编译器也要利用这个特性。另请参见JEP 181
因此,可以说JVM仍然不知道任何关于内部类的信息,因为哪些类属于一个巢,是由生成类文件的任何工具(例如Java源代码编译器)决定的。因此,可以使用其他工具生成带有巢的类文件,而不遵循内部类语义。
补充一下,类文件还包含有关内部类关系的信息,使用 InnerClasses 属性。但是,编译器和反射才会使用此信息,而JVM在决定访问是否合法时不使用此信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接