• 继承关系的类初始化和实例化的顺序


    一切都是java编译器搞得鬼. JVM只是负责解析字节码.字节码虽然不是最原始的原子汇编码,但字节码已经可以完全解释JVM的指令执行过程了。

    AD:2013云计算架构师峰会课程资料下载

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

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

    1. public class A {  
    2.     private static String a = "NA";  
    3.     private String i="NA";  
    4.     {  
    5.         i = "A";  
    6.         System.out.println(i);  
    7.     }  
    8.       
    9.     static {  
    10.         a = "Static A";  
    11.         System.out.println(a);  
    12.     }  
    13.       
    14.     public A() {  
    15.         System.out.println("Construct A");  
    16.     }  
    1. public class B extends A {  
    2.     private static String b = "NB";  
    3.     private String j="NB";  
    4.     {  
    5.         j = "B";  
    6.         System.out.println(j);  
    7.     }  
    8.       
    9.     static {  
    10.         b = "Static B";  
    11.         System.out.println(b);  
    12.     }  
    13.       
    14.     public B() {  
    15.         System.out.println("Construct B");  
    16.     }  
    1. public class C {  
    2.     public static void main(String[] args) {  
    3.         new B();  
    4.     }  
    5.  

    以上输出是:

    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的字节码,就可以找到答案.

    1. joeytekiMacBook-Air:bin joey$ javap -verbose B  
    2. Compiled from "B.java" 
    3. public class B extends A  
    4.   SourceFile: "B.java" 
    5.   minor version: 0 
    6.   major version: 50 
    7.   Constant pool:  
    8. const #1 = class    #2//  B  
    9. const #2 = Asciz    B;  
    10. const #3 = class    #4//  A  
    11. const #4 = Asciz    A;  
    12. const #5 = Asciz    b;  
    13. const #6 = Asciz    Ljava/lang/String;;  
    14. const #7 = Asciz    j;  
    15. const #8 = Asciz    <clinit>;  
    16. const #9 = Asciz    ()V;  
    17. const #10 = Asciz   Code;  
    18. const #11 = String  #12;    //  NB  
    19. const #12 = Asciz   NB;  
    20. const #13 = Field   #1.#14//  B.b:Ljava/lang/String;  
    21. const #14 = NameAndType #5:#6;//  b:Ljava/lang/String;  
    22. const #15 = String  #16;    //  Static B  
    23. const #16 = Asciz   Static B;  
    24. const #17 = Field   #18.#20;    //  java/lang/System.out:Ljava/io/PrintStream;  
    25. const #18 = class   #19;    //  java/lang/System  
    26. const #19 = Asciz   java/lang/System;  
    27. const #20 = NameAndType #21:#22;//  out:Ljava/io/PrintStream;  
    28. const #21 = Asciz   out;  
    29. const #22 = Asciz   Ljava/io/PrintStream;;  
    30. const #23 = Method  #24.#26;    //  java/io/PrintStream.println:(Ljava/lang/String;)V  
    31. const #24 = class   #25;    //  java/io/PrintStream  
    32. const #25 = Asciz   java/io/PrintStream;  
    33. const #26 = NameAndType #27:#28;//  println:(Ljava/lang/String;)V  
    34. const #27 = Asciz   println;  
    35. const #28 = Asciz   (Ljava/lang/String;)V;  
    36. const #29 = Asciz   LineNumberTable;  
    37. const #30 = Asciz   LocalVariableTable;  
    38. const #31 = Asciz   <init>;  
    39. const #32 = Method  #3.#33//  A."<init>":()V  
    40. const #33 = NameAndType #31:#9;//  "<init>":()V  
    41. const #34 = Field   #1.#35//  B.j:Ljava/lang/String;  
    42. const #35 = NameAndType #7:#6;//  j:Ljava/lang/String;  
    43. const #36 = String  #2//  B  
    44. const #37 = String  #38;    //  Construct B  
    45. const #38 = Asciz   Construct B;  
    46. const #39 = Asciz   this;  
    47. const #40 = Asciz   LB;;  
    48. const #41 = Asciz   SourceFile;  
    49. const #42 = Asciz   B.java;  
    50.  
    51. {  
    52. static {};  
    53.   Code:  
    54.    Stack=2, Locals=0, Args_size=0 
    55.    0:   ldc #11//String NB  
    56.    2:   putstatic   #13//Field b:Ljava/lang/String;  
    57.    5:   ldc #15//String Static B  
    58.    7:   putstatic   #13//Field b:Ljava/lang/String;  
    59.    10:  getstatic   #17//Field java/lang/System.out:Ljava/io/PrintStream;  
    60.    13:  getstatic   #13//Field b:Ljava/lang/String;  
    61.    16:  invokevirtual   #23//Method java/io/PrintStream.println:(Ljava/lang/String;)V  
    62.    19:  return 
    63.   LineNumberTable:   
    64.    line 30 
    65.    line 115 
    66.    line 1210 
    67.    line 1319 
    68.  
    69.  
    70.  
    71. public B();  
    72.   Code:  
    73.    Stack=2, Locals=1, Args_size=1 
    74.    0:   aload_0  
    75.    1:   invokespecial   #32//Method A."<init>":()V  
    76.    4:   aload_0  
    77.    5:   ldc #11//String NB  
    78.    7:   putfield    #34//Field j:Ljava/lang/String;  
    79.    10:  aload_0  
    80.    11:  ldc #36//String B  
    81.    13:  putfield    #34//Field j:Ljava/lang/String;  
    82.    16:  getstatic   #17//Field java/lang/System.out:Ljava/io/PrintStream;  
    83.    19:  aload_0  
    84.    20:  getfield    #34//Field j:Ljava/lang/String;  
    85.    23:  invokevirtual   #23//Method java/io/PrintStream.println:(Ljava/lang/String;)V  
    86.    26:  getstatic   #17//Field java/lang/System.out:Ljava/io/PrintStream;  
    87.    29:  ldc #37//String Construct B  
    88.    31:  invokevirtual   #23//Method java/io/PrintStream.println:(Ljava/lang/String;)V  
    89.    34:  return 
    90.   LineNumberTable:   
    91.    line 150 
    92.    line 44 
    93.    line 610 
    94.    line 716 
    95.    line 1626 
    96.    line 1734 
    97.  
    98.   LocalVariableTable:   
    99.    Start  Length  Slot  Name   Signature  
    100.    0      35      0    this       LB;  

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

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

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

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

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

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

    父类的构造函数->

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

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

    当前类的构造函数.

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

    原文链接:http://my.oschina.net/xpbug/blog/111371

  • 相关阅读:
    Linux常见故障及修复方法
    2019/作业
    使用net 模式上网的步骤
    2019 年 1
    处理请求数据
    REST 表现层状态转化
    @RequestMapping注解的属性,将请求约束精细化
    SpringMVC 概述
    基于XML文档的声明式事务配置
    事务@Transactional注解的属性
  • 原文地址:https://www.cnblogs.com/daichangya/p/12958586.html
Copyright © 2020-2023  润新知