非静态内部类与静态内部类(代码片段)

顧棟 顧棟     2023-01-03     337

关键词:

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

内部类是什么

“内部类”其实是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

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

静态非静态内部类匿名内部类局部内部类(代码片段)

内部类有静态内部类,静态内部类,匿名内部类,局部内部类(1)非静态内部类直接在类的内部定义的类就是非静态内部类,如下publicclassTestpublicstaticvoidmain(String[]args)Outer.Innerc1=newOuter().newInner();c1.Print();classOuterprivateinttemp=10;clas... 查看详情

内部类(代码片段)

内部类目录内部类#内部类#非静态内部类#静态内部类#局部内部类#内部类内部类? |--非静态内部类(成员内部类)? |--静态内部类(类内部类)? |--局部内部类? |--匿名内部类#非静态内部类相当于Outer的成员变量,可以访问实例变量,实... 查看详情

内部类(代码片段)

内部类目录内部类#内部类#非静态内部类#静态内部类#局部内部类#内部类内部类? |--非静态内部类(成员内部类)? |--静态内部类(类内部类)? |--局部内部类? |--匿名内部类#非静态内部类相当于Outer的成员变量,可以访问实例变量,实... 查看详情

内部类(代码片段)

目录非静态内部类静态内部类匿名内部类非静态内部类定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处“类内部”包括类中的任何位置,甚至方法中也可以方法里定义的内部类杯称为局部内部类。内部类定... 查看详情

内部类初识(代码片段)

...ivate、protected、static——外部类不可以使用这3个修饰符非静态内部类非静态内部类不能拥有静态成员publicclassOutClassprivateStringval="000";privateclassInClassprivateStringval="123";publicvoidshowval()System.out.println("外部类的val:&q... 查看详情

内部类之静态内部类(代码片段)

...位置  被定义在一个类下,且被static修饰二、结构  静态内部类下可以定义静态和非静态的属性和方法三、静态内部类访问外部类  1.不能访问外部类非静态的属性和方法  2.调用属性【方法】方式:    2.1直接写... 查看详情

java-内部类

...外部类可以多使用三个修饰符:private、protected、static非静态内部类不能拥有静态成员2.内部类的分类成员内部类静态内部类非静态内部类局部内部类匿名内部类3.内部类的编译  编译产生:OuterClass.class、OuterClass$InnerClass.class...... 查看详情

java中的staticclass(代码片段)

...的类可以是static吗?答案是可以。在java中我们可以有静态实例变量、静态方法、静态块。类也可以是静态的。   java允许我们在一个类里面定义静态类。比如内部类(nestedclass)。把nestedclass封闭起来的类叫外部类。... 查看详情

静态内部类定义在类中,任何方法外,用static定义(代码片段)

静态内部类:(注意:前三种内部类与变量类似,所以可以对照参考变量)静态内部类定义在类中,任何方法外,用static定义。静态内部类只能访问外部类的静态成员。生成(new)一个静态内部类不需要外部类成员:这是静态内... 查看详情

创建内部类对象(代码片段)

分别讲解内部类为静态和非静态的时候,内部类的创建方式。首先看下代码:publicclassOutClasspublicOutClass()//外部类直接创建内部类对象newInnerClass();publicvoidoutMethod()newInnerClass();System.out.println("OutClass.outMethod");/***非静态内部类*@author 查看详情

kotlin内部类与嵌套类(代码片段)

...lin内部类与嵌套类简单的说,kotlin嵌套类相当于java的静态内部类,kotlin内部类相当于java普通内部类。classOutClassvalocval="一个外部类变量值"//嵌套类,相当于Java的静态内部类classNestedClassfuntest1()="嵌套内... 查看详情

kotlin内部类与嵌套类(代码片段)

...lin内部类与嵌套类简单的说,kotlin嵌套类相当于java的静态内部类,kotlin内部类相当于java普通内部类。classOutClassvalocval="一个外部类变量值"//嵌套类,相当于Java的静态内部类classNestedClassfuntest1()="嵌套内... 查看详情

java中的四种内部类总结(代码片段)

Java的内部类主要分为四种,分别是:静态内部类、成员内部类、局部内部类、匿名内部类。静态内部类定义在类内部的静态类被称为静态内部类。静态内部类可以访问外部类的静态变量和方法;在静态内部类中可以定义静态变量、... 查看详情

java_内部类(代码片段)

在Java中内部类主要分为四种:静态内部类、成员内部类、方法内部类、匿名内部类。非静态内部类包括:成员内部类、方法内部类、匿名内部类。1.成员内部类成员内部类内部不允许存在任何static变量或方法,正如成员方法中不... 查看详情

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

...IntelliJIDEAv2018.3文章原文链接“全栈2019”Java第七十六章:静态、非静态内部类访问权限下一章“全栈2019”Java第七十七章:抽象内部类与抽象静态内部类详解学习小组加入同步学习小组,共同交流与进步。方式一:关注头条号Gorh... 查看详情

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

深入理解C#静态类与非静态类、静态成员的区别静态类静态类与非静态类的重要区别在于静态类不能实例化,也就是说,不能使用new关键字创建静态类类型的变量。在声明一个类时使用static关键字,具有两个方面的意义:首先,... 查看详情

静态类与非静态类静态成员的区别

...自森大科技官方博客http://www.cnsendblog.com/index.php/?p=521 静态类    静态类与非静态类的重要区别在于静态类不能实例化,也就是说,不能使用new关键字创建静态类类型的变量。在声明一个类时使用static关键字,具... 查看详情

8.4学习日记(代码片段)

...,KVM的某些特点让它成为了企业的首选虚拟机监控程序。静态类:所谓静态,指以static关键字修饰的,包括类,方法,块,字段。静态类和非静态类之间的区别1.内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对... 查看详情