简体   繁体   English

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

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

I have been trying to understand the mechanism of nested classes in java.我一直在尝试理解 java 中嵌套类的机制。

Consider the following code:考虑以下代码:

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

}

} }

I know that inner classes are a phenomenon of the compiler, virtual machine are not aware of them.我知道内部类是编译器的一种现象,虚拟机不知道它们。 Inner classes are translated into regular class files with $ delimiting outer and inner class name.内部类被翻译成带有 $ 分隔外部和内部类名称的常规类文件。

To understand this mechanism in more details, I tried to disassemble the class file compiled in java version 1.8 using javap -p command.为了更详细地了解这种机制,我尝试使用 javap -p 命令反汇编在 java 版本 1.8 中编译的类文件。

I got the following results: OuterClass:我得到以下结果: 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 {};
}

InnerClass:内部类:

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

Here we can see that the compiler passes the reference of outer class to inner class through constructor so that it can assess outer class's fields and methods:在这里我们可以看到编译器通过构造函数将外部类的引用传递给内部类,以便它可以评估外部类的字段和方法:

staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);

this outer class reference is stored in final staticNestedClasses.OuterClass this$0这个外部类引用存储在final staticNestedClasses.OuterClass this$0

But OuterClass$InnerClass class cannot directly access private members through outer class reference, so whenever the compiler detects access to private members from inner class, it generates accessor method (or getter methods) in outer class.但是 OuterClass$InnerClass 类不能通过外部类引用直接访问私有成员,所以每当编译器检测到内部类访问私有成员时,就会在外部类中生成访问器方法(或 getter 方法)。

In the disassembled file of outer class we can see that the compiler generated these accessor methods.在外部类的反汇编文件中,我们可以看到编译器生成了这些访问器方法。

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

But when I compiled the same code in java 17.0 and disassembled the class file I got the following result.但是当我在 java 17.0 中编译相同的代码并反汇编类文件时,我得到了以下结果。

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

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

Here compiler did not generate any accessor methods but the code worked fine.这里编译器没有生成任何访问器方法,但代码运行良好。

So how did the inner class access private members of outer class?那么内部类是如何访问外部类的私有成员的呢?

The only thing that stops a class from accessing another class's private members, is the JVM (or precisely its verifier) rejecting the access.阻止一个类访问另一个类的private成员的唯一方法是 JVM(或者确切地说是它的验证器)拒绝访问。 So all it needs to make it possible, is the collaboration of the JVM to allow it.因此,实现它所需要的只是 JVM 的协作来允许它。

While Java 1.1 introduced inner classes in a way that did not require changes to the JVM, the JVM has gone through so many changes in the meanwhile, that it is rather surprising that it took until Java 11 to change that.虽然 Java 1.1 以不需要更改 JVM 的方式引入了内部类,但同时 JVM 经历了如此多的变化,以至于直到 Java 11 才改变这一点是相当令人惊讶的。

Java 11 introduced the NestHost and NestMembers bytecode attributes to allow class files to denote that they belong to a so called “nest”. Java 11 引入了NestHostNestMembers字节码属性,以允许类文件表示它们属于所谓的“巢”。 All classes belonging to the same nest are allowed to access each others private members.属于同一个嵌套的所有类都可以访问彼此的private成员。 As said, the only thing that needed to be changed, is the JVM's verifier to allow such access.如前所述,唯一需要更改的是 JVM 的验证程序以允许此类访问。 And, of course, the compiler to utilize this feature.而且,当然,编译器要利用这个特性。 See also JEP 181 .另见JEP 181

So you could say that the JVM still doesn't know anything about inner classes, because which classes belong to a nest, is decided by whichever tool generated the class files (eg the Java source code compiler).所以你可以说JVM仍然对内部类一无所知,因为哪些类属于一个嵌套,由生成类文件的任何工具(例如Java源代码编译器)决定。 So it is possible to produce class files with other tools using nests without following the inner class semantic.因此,可以使用嵌套的其他工具生成类文件,而无需遵循内部类语义。

For completion, it should be mentioned that class files do also contain the information about inner class relationships, using the InnerClasses attribute.为了完成,应该提到类文件还包含有关内部类关系的信息,使用InnerClasses属性。 But this is only used by compilers and Reflection, whereas the JVM doesn't use this information when deciding whether an access is legal or not.但这仅由编译器和反射使用,而 JVM 在决定访问是否合法时不使用此信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM