非静态内部类与静态内部类

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了非静态内部类与静态内部类相关的知识,希望对你有一定的参考价值。

非静态内部类与静态内部类

内部类是什么

“内部类”其实是java语言层面的概念,在JVM中并不关心一个类是宿主类还是内部类。

通过编译器编译java文件会产生宿主类的class文件和内部类的class文件,对于,JVM最终加载的是class文件。

通过javap命令查看宿主类的class文件和内部类的class文件,JDK8 JDK11的编译的class的不同:主要增加了Nestmates结构,在编译和运行时说明类的嵌套关系。

内部类解析

实例代码

public class OuterClass {
    public class InnerClass {
    }
}

使用javap 命令进行class文件的反编译

JDK 8

宿主类

E:\\code\\demos\\datastructure\\target\\classes\\jvm> javap -c -v '.\\OuterClass.class'
Classfile /E:/code/demos/datastructure/target/classes/jvm/OuterClass.class
  Last modified 2021-9-8; size 394 bytes
  MD5 checksum dffa496f9469e320e67d154241a7522a
  Compiled from "OuterClass.java"
public class jvm.OuterClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // jvm/OuterClass
   #3 = Class              #20            // java/lang/Object
   #4 = Class              #21            // jvm/OuterClass$InnerClass2
   #5 = Utf8               InnerClass2
   #6 = Utf8               InnerClasses
   #7 = Class              #22            // jvm/OuterClass$InnerClass1
   #8 = Utf8               InnerClass1
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Ljvm/OuterClass;
  #16 = Utf8               SourceFile
  #17 = Utf8               OuterClass.java
  #18 = NameAndType        #9:#10         // "<init>":()V
  #19 = Utf8               jvm/OuterClass
  #20 = Utf8               java/lang/Object
  #21 = Utf8               jvm/OuterClass$InnerClass2
  #22 = Utf8               jvm/OuterClass$InnerClass1
{
  public jvm.OuterClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/OuterClass;
}
SourceFile: "OuterClass.java"
InnerClasses:
     public #5= #4 of #2; //InnerClass2=class jvm/OuterClass$InnerClass2 of class jvm/OuterClass
     public #8= #7 of #2; //InnerClass1=class jvm/OuterClass$InnerClass1 of class jvm/OuterClass
PS E:\\code\\demos\\datastructure\\target\\classes\\jvm>

在InnerClasses属性中表明了这个宿主类有两个内部类

内部类

 E:\\code\\demos\\datastructure\\target\\classes\\jvm> javap -v -c '.\\OuterClass$InnerClass1.class'
Classfile /E:/code/demos/datastructure/target/classes/jvm/OuterClass$InnerClass1.class
  Last modified 2021-9-8; size 465 bytes
  MD5 checksum 705055d28382a2d881cbdcaa7717d717
  Compiled from "OuterClass.java"
public class jvm.OuterClass$InnerClass1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Fieldref           #3.#19         // jvm/OuterClass$InnerClass1.this$0:Ljvm/OuterClass;
   #2 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #3 = Class              #22            // jvm/OuterClass$InnerClass1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               this$0
   #6 = Utf8               Ljvm/OuterClass;
   #7 = Utf8               <init>
   #8 = Utf8               (Ljvm/OuterClass;)V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               InnerClass1
  #14 = Utf8               InnerClasses
  #15 = Utf8               Ljvm/OuterClass$InnerClass1;
  #16 = Utf8               MethodParameters
  #17 = Utf8               SourceFile
  #18 = Utf8               OuterClass.java
  #19 = NameAndType        #5:#6          // this$0:Ljvm/OuterClass;
  #20 = NameAndType        #7:#24         // "<init>":()V
  #21 = Class              #25            // jvm/OuterClass
  #22 = Utf8               jvm/OuterClass$InnerClass1
  #23 = Utf8               java/lang/Object
  #24 = Utf8               ()V
  #25 = Utf8               jvm/OuterClass
{
  final jvm.OuterClass this$0;
    descriptor: Ljvm/OuterClass;
    flags: ACC_FINAL, ACC_SYNTHETIC

  public jvm.OuterClass$InnerClass1(jvm.OuterClass);
    descriptor: (Ljvm/OuterClass;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field this$0:Ljvm/OuterClass;
         5: aload_0
         6: invokespecial #2                  // Method java/lang/Object."<init>":()V
         9: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Ljvm/OuterClass$InnerClass1;
            0      10     1 this$0   Ljvm/OuterClass;
    MethodParameters:
      Name                           Flags
      this$0                         final mandated
}
SourceFile: "OuterClass.java"
InnerClasses:
     public #13= #3 of #21; //InnerClass1=class jvm/OuterClass$InnerClass1 of class jvm/OuterClass

InnerClasses表明了当前内部类的关系

JDK 11

宿主类


在JDK11开始class文件中除了InnerClasses展示了内部类的列表,在宿主类中还有NestMembers属性表明有哪些内部类

内部类

在JDK11开始class文件中除了InnerClasses展示了内部类的列表,在内部类中还有NestHost属性表明有自己的宿主类

NestHost和NestMembers是在JDK11新增的特性用于支持嵌套类的反射和访问控制的API,宿主类需要知道自己有哪些内部类,内部类需要知道自己的宿主类。

加载的顺序的验证

实现代码:

public class OuterClass {
    public static long OUTER_DATE = System.nanoTime();

    static {
        System.out.println("宿主类静态块加载时间:" + System.nanoTime());
    }

    public OuterClass() {
        System.out.println("宿主类构造函数时间:" + System.nanoTime());
    }

    public static class InnerStaticClass {
        static {
            System.out.println("静态内部类静态块加载时间:" + System.nanoTime());
        }

        public InnerStaticClass(){
            System.out.println("静态内部类构造函数时间:" + System.nanoTime());
        }
        
        public static long INNER_STATIC_DATE = System.nanoTime();
    }

    public class InnerClass {
        public long INNER_DATE = 0;

        public InnerClass() {
            INNER_DATE = System.nanoTime();
        }
    }

    public static void main(String[] args) {
        // 当宿主类内静态变量被调用时
        OuterClass outer = new OuterClass();
        System.out.println("宿主类静态变量加载时间:" + OuterClass.OUTER_DATE);
        // 非静态内部类的内静态变量被调用时
        System.out.println("非静态内部类的内静态变量加载时间:" + outer.new InnerClass().INNER_DATE);
        // 静态内部类中的变量被调用时
        System.out.println("静态内部类加载时间:" + InnerStaticClass.INNER_STATIC_DATE);
    }
}

执行:宿主类内静态变量被调用

public static void main(String[] args) {
        // 当宿主类内静态变量被调用时
        OuterClass outer = new OuterClass();
        System.out.println("宿主类静态变量加载时间:" + OuterClass.OUTER_DATE);
    }
宿主类静态块加载时间:2849307614761374
宿主类构造函数时间:2849307615122719
宿主类静态变量加载时间:2849307614510661

通过new创建了一个宿主类对象,会先进行宿主类的类加载过程,再去执行对象的创建过程。

宿主类的静态变量与静态代码块会先去初始化,再去执行构造函数。内部类不执行类的加载过程。其实JVM去执行类的<clinet>()方法是编译器自动收集所有类变量的赋值和静态语句块的语句合并产生的。收集的顺序与源文件出现的顺序一致。也就是说 宿主类静态块和宿主类静态变量哪个在前,哪个先执行。

执行:非静态内部类的静态变量被调用

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        // 非静态内部类的静态变量被调用时
        System.out.println("非静态内部类的内静态变量加载时间:" + outer.new InnerClass().INNER_DATE);
    }
宿主类静态块加载时间:2854794125231882
宿主类构造函数时间:2854794126235869
非静态内部类的内静态变量加载时间:2854794127427514

明显的,非静态内部类的加载情况与宿主类一致,在使用静态内部类的时候会先去完成载宿主类的类加载过程和对象的创建过程,在去完成非静态内部类的类加载过程和对象创建过程。

通过这种使用内部类的方式会出现内部泄露的风险

执行:静态内部类中的变量被调用时

    public static void main(String[] args) {
        // 静态内部类中的变量被调用时
        System.out.println("静态内部类加载时间:" + InnerStaticClass.INNER_STATIC_DATE);
    }
宿主类静态块加载时间:2855196672844268
静态内部类静态块加载时间:2855196680637538
静态内部类加载时间:2855196680723248

可以发现,静态内部类中的静态对象的调用,只完成了宿主类的类加载过程和静态内部类的类加载过程,并没有执行宿主类中其他内部类的类加载过程,和宿主类以及静态内部类的对象创建过程。

以上是关于非静态内部类与静态内部类的主要内容,如果未能解决你的问题,请参考以下文章

内部类

java-内部类

深入理解C# 静态类与非静态类静态成员的区别

静态类和内部类的区别是啥

“全栈2019”Java第七十六章:静态非静态内部类访问权限

Java内部类:静态内部类&接口内部类