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

}

}

我知道内部类是编译器的一种现象,虚拟机并没有意识到它们.内部类被转换为常规类文件,外部类名和内部类名之间用$分隔.

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

我得到了以下结果:

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$内部类别:

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

这里是compiler did not generate any accessor methods,但代码运行良好.

So how did the inner class access private members of outer class?

推荐答案

阻止一个类访问另一个类的private个成员的唯一方法是JVM(确切地说是它的验证器)拒绝访问.因此,它所需要的只是JVM的协作来允许它.

虽然Java1.1以一种不需要对JVM进行更改的方式引入了内部类,但JVM同时经历了如此多的更改,以至于直到Java11才改变这一点,这相当令人惊讶.

Java11引入了NestHostNestMembers字节码属性,允许类文件表示它们属于所谓的"嵌套".属于同一巢穴的所有类都可以相互访问private个成员.如前所述,唯一需要更改的是JVM的验证器,以允许这种访问.当然,编译器也可以利用这个特性.另见JEP 181.

因此,可以说JVM仍然不知道任何关于内部类的信息,因为哪些类属于嵌套,取决于生成类文件的工具(例如Java源代码编译器).因此,在不遵循内部类语义的情况下,可以使用其他工具使用嵌套生成类文件.

作为补充,应该提到的是,类文件也包含关于内部类关系的信息,使用InnerClasses属性.但这只供编译器和反射使用,而JVM在决定访问是否合法时不使用这些信息.

Java相关问答推荐

如何将一些命令写入Chrome控制台,然后使用Java将输出存储在selenium中

无法在Java中使用Curve secp 256 k1验证JWT

将具有多个未知字段的SON映射到Java POJO

当切换javaFX场景时,stage的大小正在Minimize

如果一个子类没有构造函数,超类也没有构造函数,那么为什么我可以构造子类的实例呢?

使用JdkClientHttpRequestFactory通过Spring RestClient和Wiemock读取时达到EOF

这是什么Java构造`(InputStream Is)->;()->;{}`

查找剩余的枚举

由于我在Main方法中关闭了 scanner ,但在该方法中创建了一个新的 scanner ,因此出现了错误

%This内置函数示例

如何使用Criteria Builder处理一对多关系中的空值?

有谁能帮我修一下这个吗?使输出变得更加整洁

如果List是一个抽象接口,那么Collectors.toList()如何处理流呢?

从Spring6中的JPMS模块读取类时出现问题

如何在SWT菜单项文本中保留@字符

如何调整JButton的大小以适应图标?

Maven-Dependency-Plugin 3.6.+开始查找在依赖关系:分析目标期间找到的新的使用的未声明依赖关系

如何在MPAndroidChart中的条形图上正确添加标签

在输入端没有可行的替代方案'; Select *';

";重复键的值提示唯一约束«;livre_genre_pkey»";例外