iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >java继承关系的类初始化和实例化顺序是怎样的
  • 440
分享到

java继承关系的类初始化和实例化顺序是怎样的

2023-06-17 08:06:58 440人浏览 独家记忆
摘要

这期内容当中小编将会给大家带来有关java继承关系的类初始化和实例化顺序是怎样的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。就像之前的一个评论.我们学习的是思路. 很多人都知道继承关系的类的初始化和实例

这期内容当中小编将会给大家带来有关java继承关系的类初始化和实例化顺序是怎样的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

就像之前的一个评论.我们学习的是思路. 很多人都知道继承关系的类的初始化和实例化的顺序,但如果忘记了怎么办? 如何找到自己的答案? 又如果遇到的问题是关于泛型的擦除问题,又该如何去分析?

思路,重点是思路.泛型擦除先不谈.看继承. 首先给出一个例子,看看它的输出是什么.

public class A {      private static String a = "NA";      private String i="NA";      {          i = "A";          System.out.println(i);      }            static {          a = "Static A";          System.out.println(a);      }            public A() {          System.out.println("Construct A");      }  }
public class B extends A {      private static String b = "NB";      private String j="NB";      {          j = "B";          System.out.println(j);      }            static {          b = "Static B";          System.out.println(b);      }            public B() {          System.out.println("Construct B");      }  }
public class C {      public static void main(String[] args) {          new B();      }   }

以上输出是:

Static A
Static B
A
Construct A
B
Construct B

一切都是java编译器搞得鬼. JVM只是负责解析字节码.字节码虽然不是最原始的原子汇编码,但字节码已经可以完全解释JVM的指令执行过程了.一般来说,字节码和java源码相差比较大,javac会做前期优化,修改增加删除源码产生jvm解释器可以理解的字节码. java语法带来的安全,易用,易读等功能让我们忽略了字节码会和java源码有出路.

当遇到new的时候,比如new B(),将会尝试去初始化B类.如果B已经初始化,则开始实例化B类.如果B类没有初始化,则初始化B类,但B类继承A,所以在初始化B类之前需要先初始化A类.所以类的初始化过程是:A->B. 类在初始化的时候会执行static域和块. 类的实例化在类初始化之后,实例化的时候必须先实例化父类.实例化会先执行域和块,然后再执行构造函数.

上面的理论如果靠这种死记硬背,总会忘记.哦,还有父类的构造函数必须放在子类构造函数的***行.为什么?

遇到这种语法问题的时候,看教科书不如自己找出答案.工具就在jdk中,一个名叫javap的命令. javap会打出一个class的字节码伪码. 我们只需要分析B的字节码,就可以找到答案.

joeytekiMacBook-Air:bin joey$ javap -verbose B  Compiled from "B.java" public class B extends A    SourceFile: "B.java"   minor version: 0   major version: 50   Constant pool:  const #1 = class    #2; //  B  const #2 = Asciz    B;  const #3 = class    #4; //  A  const #4 = Asciz    A;  const #5 = Asciz    b;  const #6 = Asciz    Ljava/lang/String;;  const #7 = Asciz    j;  const #8 = Asciz    <clinit>;  const #9 = Asciz    ()V;  const #10 = Asciz   Code;  const #11 = String  #12;    //  NB  const #12 = Asciz   NB;  const #13 = Field   #1.#14; //  B.b:Ljava/lang/String;  const #14 = NameAndType #5:#6;//  b:Ljava/lang/String;  const #15 = String  #16;    //  Static B  const #16 = Asciz   Static B;  const #17 = Field   #18.#20;    //  java/lang/System.out:Ljava/io/PrintStream;  const #18 = class   #19;    //  java/lang/System  const #19 = Asciz   java/lang/System;  const #20 = NameAndType #21:#22;//  out:Ljava/io/PrintStream;  const #21 = Asciz   out;  const #22 = Asciz   Ljava/io/PrintStream;;  const #23 = Method  #24.#26;    //  java/io/PrintStream.println:(Ljava/lang/String;)V  const #24 = class   #25;    //  java/io/PrintStream  const #25 = Asciz   java/io/PrintStream;  const #26 = NameAndType #27:#28;//  println:(Ljava/lang/String;)V  const #27 = Asciz   println;  const #28 = Asciz   (Ljava/lang/String;)V;  const #29 = Asciz   LineNumberTable;  const #30 = Asciz   LocalVariableTable;  const #31 = Asciz   <init>;  const #32 = Method  #3.#33; //  A."<init>":()V  const #33 = NameAndType #31:#9;//  "<init>":()V  const #34 = Field   #1.#35; //  B.j:Ljava/lang/String;  const #35 = NameAndType #7:#6;//  j:Ljava/lang/String;  const #36 = String  #2; //  B  const #37 = String  #38;    //  Construct B  const #38 = Asciz   Construct B;  const #39 = Asciz   this;  const #40 = Asciz   LB;;  const #41 = Asciz   SourceFile;  const #42 = Asciz   B.java;   {  static {};    Code:     Stack=2, Locals=0, Args_size=0    0:   ldc #11; //String NB     2:   putstatic   #13; //Field b:Ljava/lang/String;     5:   ldc #15; //String Static B     7:   putstatic   #13; //Field b:Ljava/lang/String;     10:  getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;     13:  getstatic   #13; //Field b:Ljava/lang/String;     16:  invokevirtual   #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V     19:  return   LineNumberTable:      line 3: 0    line 11: 5    line 12: 10    line 13: 19    public B();    Code:     Stack=2, Locals=1, Args_size=1    0:   aload_0     1:   invokespecial   #32; //Method A."<init>":()V     4:   aload_0     5:   ldc #11; //String NB     7:   putfield    #34; //Field j:Ljava/lang/String;     10:  aload_0     11:  ldc #36; //String B     13:  putfield    #34; //Field j:Ljava/lang/String;     16:  getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;     19:  aload_0     20:  getfield    #34; //Field j:Ljava/lang/String;     23:  invokevirtual   #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V     26:  getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;     29:  ldc #37; //String Construct B     31:  invokevirtual   #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V     34:  return   LineNumberTable:      line 15: 0    line 4: 4    line 6: 10    line 7: 16    line 16: 26    line 17: 34    LocalVariableTable:      Start  Length  Slot  Name   Signature     0      35      0    this       LB;  }

类的生命周期,将经历类的装载,链接,初始化,使用,卸载. 装载是将字节码读入到内存的方法区中, 而类的初始化则会在线程栈中执行static{}块的code. 在之前,这个块有另一个名字<cinit>即类初始化方法.现在改名为static{}了. 类的初始化只进行一次. 但是,每当一个类在装载和链接完毕以后,通过字节码的分析,JVM解析器已经知道B是继承A的,于是在初始化B类前,A类会先初始化.这是一个递归过程. 所以,B类的初始化会导致A类static{}执行,然后是B的static{}执行.让我们看看B的static{}块中执行了什么.

static {};    Code:     Stack=2, Locals=0, Args_size=0 栈深为2,本地变量0个,参数传递0个.     0:   ldc #11; //String NB  将常量池中#11放到栈顶.#11="NB".     2:   putstatic   #13; //Field b:Ljava/lang/String;  将栈顶的值 "NB" 赋予常量池中的#13,也就是 static b="NB".     5:   ldc #15; //String Static B  将#15放入栈顶. #15="static B".     7:   putstatic   #13; //Field b:Ljava/lang/String;  赋值static b = "static B".     10:  getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;  将PrintStream引用压栈.     13:  getstatic   #13; //Field b:Ljava/lang/String;  将static b的值压栈.     16:  invokevirtual   #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  调用虚函数PrintStream.println("static B")     19:  return 退出函数,销毁函数栈帧.

通过注释,我们看到类B中的static域赋值和static块均被放到了类的初始化函数中.

当我们进行类的实例化的时候,会调用类的构造函数.我们看看类B的构造函数做了什么.

public B();    Code:     Stack=2, Locals=1, Args_size=1 栈深为2,本地变量1个(其实就是this),参数为1个(就是this).     0:   aload_0  将***个参数压栈.也就是this压栈.     1:   invokespecial   #32; //Method A."<init>":()V  在this上调用父类的构造函数.在B的构造函数中并没有声明super(),但是java编译器会自动生成此字节码来调用父类的无参构造函数.如果在B类中声明了super(int),编译器会使用对应的A类构造函数来代替.JVM只是执行字节码而已,它并不对super进行约束,约束它们的是java的编译器.this出栈.     4:   aload_0  将this压栈.     5:   ldc #11; //String NB  将"NB"压栈.     7:   putfield    #34; //Field j:Ljava/lang/String;  给j赋值this.j="NB". this和"NB"出栈.     10:  aload_0  将this压栈.     11:  ldc #36; //String B  把"B"压栈     13:  putfield    #34; //Field j:Ljava/lang/String;  给j赋值this.j="B". this和"B"出栈.栈空     16:  getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;  压栈PrintStream     19:  aload_0  压栈this    20:  getfield    #34; //Field j:Ljava/lang/String;  this出栈,调用this.j,压栈this.j.     23:  invokevirtual   #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  调用PrintStream.println(this.j).栈空.     26:  getstatic   #17; //Field java/lang/System.out:Ljava/io/PrintStream;  压栈PrintStream     29:  ldc #37; //String Construct B  压栈"Construct B"    31:  invokevirtual   #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  调用PrintStream.println("Construct B")     34:  return

从上面的字节码可以看出,java编译器在编译产生字节码的时候,将父类的构造函数,域的初始化,代码块的执行和B的真正的构造函数按照顺序组合在了一起,形成了新的构造函数. 一个类的编译后的构造函数字节码一定会遵循这样的顺序包含以下内容:

父类的构造函数->

当前类的域初始化->(按照书写顺序)

代码块->(按照书写顺序)

当前类的构造函数.

到这里,应该彻底明白继承类的初始化和实例化顺序了.

上述就是小编为大家分享的java继承关系的类初始化和实例化顺序是怎样的了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网精选频道。

--结束END--

本文标题: java继承关系的类初始化和实例化顺序是怎样的

本文链接: https://www.lsjlt.com/news/288054.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • c#文本框只读属性怎么设置
    c# 文本框只读属性的设置 问题:如何设置 C# 文本框的只读属性? 回答: 要设置文本框的只读属性,可以使用 ReadOnly 属性。 详细解释: ReadOnly 属性是一个布尔值属...
    99+
    2024-05-14
    c#
  • 如何使用 Golang ORM 工具与数据库交互?
    使用 gorm orm 工具与数据库交互,可通过以下步骤轻松实现:安装和初始化(1)、定义模型(2)、建立映射(3)、创建记录(4)、读取记录(5)、更新记录(6)、删除记录(7)、事务...
    99+
    2024-05-14
    golang orm mysql git iphone
  • c++中double与float的区别
    c++++ 中 double 与 float 的区别 在 C++ 中,double 和 float 都是浮点数类型,但它们在精度、范围和内存占用方面存在差异。 精度: double:双...
    99+
    2024-05-14
    c++ 内存占用
  • 如何在 Golang 中处理数据库错误?
    在 go 中处理数据库错误的步骤包括:使用专门的 go mysql 驱动程序。实现 error 接口以创建自定义错误。检测错误,记录足够的信息,并基于错误类型执行适当的恢复操作。 如何...
    99+
    2024-05-14
    golang 数据库错误 mysql git 数据丢失
  • c++中int怎么转string
    在 c++ 中将 int 转换为 string 的方法有:使用 to_string() 函数直接转换。使用 stringstream 类。使用 sprintf() 函数。 如何在 C+...
    99+
    2024-05-14
    c++
  • 优化 C++ 服务器架构以提高吞吐量
    优化 c++++ 服务器吞吐量策略:线程池:预先创建线程池,快速响应请求。非阻塞 i/o:在等待 i/o 时执行其他任务,提升吞吐量。http/2:使用二进制协议,支持多路复用和内容压缩...
    99+
    2024-05-14
    优化 服务器架构 c++
  • 使用 C++ 堆分配器管理服务器架构中的内存
    使用 c++++ 堆分配器管理服务器内存可提高性能和稳定性。堆分配器负责分配和释放动态内存,跟踪空闲/已分配内存元数据。在服务器架构中,它用于分配应用程序对象、缓冲区和数据结构。选择堆分...
    99+
    2024-05-14
    c++ 内存管理 并发访问
  • c#怎么获取字符串中的数字
    从 c# 字符串中提取数字的方法有五种:正则表达式、循环和 char.isdigit()、int.tryparse()、string.split() 和 int.parse()、linq...
    99+
    2024-05-14
    git c#
  • C++ 异常处理在服务器架构中的最佳实践
    c++++ 异常处理在服务器架构的最佳实践:定义清晰的异常层次结构,使用自定义异常类型封装相关信息。使用异常安全函数,及时在适当范围内处理异常。提供有意义的错误消息,帮助用户了解错误并采...
    99+
    2024-05-14
    c++ 异常处理
  • c#怎么拼接字符串
    在 c# 中拼接字符串有三种方法:使用加法(+)运算符、string.concat() 方法和 stringbuilder 类。最简单的方法是使用 + 运算符将字符串连接起来,...
    99+
    2024-05-14
    c#
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作