【java基础☞初始化顺序】java继承中的初始化顺序

原创
2019/06/25 14:08
阅读数 140

1、初始化顺序:父类的静态变量-->父类的静态代码块-->子类的静态变量-->子类的静态代码快-->父类的非静态变量(父类的非静态代码块)-->父类的构造函数-->子类的非静态变量(子类的非静态代码块)-->子类的构造函数

重点关注:父类的非静态变量(父类的非静态代码块)是同一级的,看哪个部分写在类的最前面谁就先被执行,子类的非静态变量(子类的非静态代码块)也是一样。

package com.test;


public class Test {
    public static void main(String[] args) {
        Child c=new Child();
    }
}
class Parent {
    public static PrintMessage a=new PrintMessage("父类静态成员被初始化");
    private PrintMessage b=new PrintMessage("父类非静态成员被初始化"); 
    static{
        System.out.println("父类的静态代码块被执行");
    }
    {
        System.out.println("父类的非静态代码块被执行");
    }
    public Parent(){
        System.out.println("父类的构造方法被执行");
    }   
}

class Child extends Parent{
    public static PrintMessage a1=new PrintMessage("子类静态成员被初始化");
    private PrintMessage b1=new PrintMessage("子类非静态成员被初始化");    
    
    static {
        System.out.println("子类的静态代码块被执行");
    }
    {
        System.out.println("子类的非静态代码块被执行");
    }
    public Child(){
        System.out.println("子类的构造函数被执行");
    }
}

class PrintMessage{
    public PrintMessage(String mes){
        System.out.println(mes);
    }
}

执行结果:

父类静态成员被初始化
父类的静态代码块被执行
子类静态成员被初始化
子类的静态代码块被执行
父类非静态成员被初始化
父类的非静态代码块被执行
父类的构造方法被执行
子类非静态成员被初始化
子类的非静态代码块被执行
子类的构造函数被执行

 

2、JAVA 类的加载机制

Java类加载分为5个过程,分别为:加载,连接(验证,准备,解析),初始化,使用,卸载。

1. 加载
    加载主要是将.class文件(也可以是zip包)通过二进制字节流读入到JVM中。 在加载阶段,JVM需要完成3件事:
    1)通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存。
    2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
    3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2.连接

    2.1. 验证
    主要确保加载进来的字节流符合JVM规范。验证阶段会完成以下4个阶段的检验动作:
    1)文件格式验证
    2)元数据验证(是否符合Java语言规范)
    3)字节码验证(确定程序语义合法,符合逻辑)
    4)符号引用验证(确保下一步的解析能正常执行)
    2.2. 准备
    准备是连接阶段的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。
    2.3. 解析
    解析是连接阶段的第三步,是虚拟机将常量池内的符号引用替换为直接引用的过程。

3. 初始化

    初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。
    当有继承关系时,先初始化父类再初始化子类,所以创建一个子类时其实内存中存在两个对象实例。
 注:如果类的继承关系过长,单从类初始化角度考虑,这种设计不太可取。原因我想你已经猜到了。
    通常建议的类继承关系最多不超过三层,即父-子-孙。某些特殊的应用场景中可能会加到4层,但就此打住,第4层已经有代码设计上的弊端了。

4. 使用
    程序之间的相互调用。

5. 卸载
    即销毁一个对象,一般情况下中有JVM垃圾回收器完成。代码层面的销毁只是将引用置为null。

通过上面的整体介绍后,再来看Singleton2.getInstance()的执行分析:
    1)类的加载。运行Singleton2.getInstance(),JVM在首次并没有发现Singleton类的相关信息。所以通过classloader将Singleton.class文件加载到内存中。
    2)类的验证。略
    3)类的准备。将Singleton2中的静态资源转化到方法区。value1,value2,singleton在方法区被声明分别初始为0,0,null。
    4)类的解析。略(将常量池内的符号引用替换为直接引用的过程)
    5)类的初始化。执行静态属性的赋值操作。按照顺序先是value1 = 5,value2 = 3,接下来是private static Singleton2 singleton2 = new Singleton2();
这是个创建对象操作,根据 结论1 在执行Singleton2的构造方法之前,先去执行static资源和非static资源。但由于value1,value2已经被初始化过,所以接下来执行的是非static的资源,最后是Singleton2的构造方法:value1++;value2++。
所以Singleton2结果是6和4。

以上除了搞清楚执行顺序外,还有一个重点->结论2:静态资源在类的初始化中只会执行一次。不要与第3个步骤混淆。

有了以上的这个结论,再来看Singleton.getInstance()的执行分析:
    1)类的加载。将Singleton类加载到内存中。
    2)类的验证。略
    3)类的准备。将Singleton2的静态资源转化到方法区。
    4)类的解析。略(将常量池内的符号引用替换为直接引用的过程)
    5)类的初始化。执行静态属性的赋值操作。按照顺序先是private static Singleton singleton = new Singleton(),根据 结论1 和结论2,value1和value2不会在此层执行赋值操作。所以singleton对象中的value1,value2只是在0的基础上进行了++操作。此时singleton对象中的value1=1,value2=1。
然后, public static int value1 = 5; public static int value2 = 3; 这两行代码才是真的执行了赋值操作。所以最后的结果:5和3。
如果执行的是public static int value1; public static int value2;结果又会是多少?结果: 1和1。

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部