java静态初始化块,初始化块和构造器关系

1.下面代码执行顺序(直接由程序入手)

  public class javaTest2 {
    //父静态初始化块
    static {
        System.out.println("parent 静态代码块");
    }
    //父初始化块
    {
        System.out.println("parent 代码块");
    }
    //父构造器
    public javaTest2(){
        System.out.println("parent 构造函数");
    }

}

class JavaTest2Son extends javaTest2{
    //子静态初始化块
    static {
        System.out.println("son 静态代码块");
    }
    //子初始化块
    {
        System.out.println("son 代码块");
    }
    //子构造器
    public JavaTest2Son(){
        System.out.println("son 构造函数");
    }
    //子普通方法
    public void sonMethod(){
       
        {
            System.out.println("son 普通方法执行代码块1");
        }

        System.out.println("son 普通方法执行逻辑");

        {
            System.out.println("son 普通方法执行代码块2");
        }

    }

    public static void main(String[] args) {
        System.out.println("main 线程执行");
        JavaTest2Son javaTest2Son = new JavaTest2Son();
        javaTest2Son.sonMethod();
        System.out.println("main 线程执行2");
    }
}

2.执行结果:

parent 静态代码块
son 静态代码块
main 线程执行1
parent 代码块
parent 构造函数
son 代码块
son 构造函数
son 普通方法执行代码块1
son 普通方法执行逻辑
son 普通方法执行代码块2
main 线程执行2

3.分析

  • 上面的代码第一次看也有点炸毛,不过听笔者分析一下,你就懂了,而且不仅让你懂,还知道为什么。
  • 分析代码:
    3.1. 首先上面有两个类,javaTest2 类和JavaTest2Son类,看名字可以知道,JavaTest2Son继承了javaTest2 类。
    3.2. javaTest2类中有三部分,也就是我们要讨论的三个,静态初始化块,初始化块和构造器。
    3.3. javaTest2Son类中相似,只是多了一个普通方法。
    3.4. mian()方法中在创建javaTest2对象的前后分别进行打印
  • 分析结果
    3.5. 静态代码块优先执行,且由于继承继承关系,先执行父类再执行子类
    3.6. 之后执行程序入口,按照顺序执行mian的第一个打印方法,再执行对象里面的各种操作,再执行mian第二个打印方法。
    3.7. 重点就是创建对象后执行的方法顺序,会发现,父类先执行,子类后执行。
    3.8. 在父类与子类中,又是先执行代码块,再执行构造方法。
    3.9. 为什么不说普通方法?因为前面是对象初始化的时候的,而普通方法是在调用对象的方法的时候执行的,顺序就不言而喻了,毕竟没盖好房子怎么住人?
  • 根据分析可得:


    java静态初始化块,初始化块和构造器关系_第1张图片
    分析结果

4.结论

1.java源码的执行流程

①.编译:jvm虚拟机先将我们的源码编译成Class文件。分成三个步骤:
  1.分析和输入到符号表;
  2.注解处理;
  3.语义分析和生成Class文件
②.类加载:之后将Class文件加载到jvm内存中去,分成三个步骤:
  1.装载:查找和导入类或接口的二进制数据;
  2.链接:分成三步:
    2.1.验证:检查导入类或接口的二进制数据的正确性;
    2.2.准备:给类的静态变量分配并初始化存储空间,分配默认值
    2.3.解析:为类和方法等定位直接引用。完成内存结构布局
  3.初始化:为静态变量赋值(用户规定的),执行静态代码块中的内容,这些被static修饰的静态代码块和静态变量在编译时被放入类/接口初始化方法中,在本步骤覆盖准备阶段的默认初始值。

2.为什么静态初始化块在函数执行前执行?

因为静态初始化块在类加载的过程就被执行了,而且在类加载过程中jvm会保证父类先被加载,再加载子类,因此静态初始化块又被称为类初始化块,是与类相关的操作,对类进行初始化处理时执行的。而且类初始化块只执行一次。当再次使用该类创建实例的时候,就不会执行静态初始化块了

3.初始化块与构造器有什么关系?

3.1 相似点:

①.两者都是在创建对象实例时,对该对象进行初始化操作,所以两者都是作用于对象级别的,负责对对象进行初始化。
②.一个类中可以包含多个构造方法(重载),还可以包含多个初始化块。但是每创建一个实例,只能执行一个构造方法,但是所有的初始化块都会执行(根据定义的顺序)。
③.初始化块和构造方法中都可以执行定义局部变量,调用其它方法使用分支循环等操作。

3.2 不同点:

①.初始化块只能在创建对象的时,隐式执行,而且在构造器之前执行。
②.初始化块会对所有对象进行相同的处理,而构造器可以根据使用不同重载方法来实现初始化的不同。
③.初始化块主要作用是,对于多个重载构造器都需要执行的代码,且这部分代码不接受任何参数进行提取,使代码更简洁。

4.为什么初始化块在构造器之前执行?

实质上初始化块仅仅是java中,对于我们看见的源码而言有所区别,其核心实质就是构造器的一部分。在java源码被编译后,java中的所有初始化块都会消失,且初始化块中的代码会加入到该java类的构造器中,且位于构造器所有代码的前面。

5.写在方法中的初始化块有什么用?

答案是没什么软用,你就当它不存在,按照代码的逻辑顺序执行就可以。要说唯一的价值可能就是,烟雾弹迷糊你。但是有一点,静态初始化块不能写在方法中,因为静态的是类级别的操作,对象方法级别不够。只有初始化块可以写,虽然没软用


5.扩展

5.1.问题

  • 初始化块,声明实例变量(类的成员变量都是对象级别的)指定的默认值都是对对象的初始化,那么若声明的一个变量且初始化了,再使用初始化块对其进行初始化,会执行哪个结果?
public class JavaTest3 {
    //先执行初始化块的初始化,再执行声明实例变量的默认初始化
    {
        a = 6;
    }
    int a = 3;
    
    //先执行声明实例变量的默认初始化,再执行初始化块的初始化
    int b = 3;
    {
        b = 6;
    }
    
    //先执行声明实例变量的默认初始化(或初始化块的初始化),最后在构造器中进行赋值
    int c = 5;
    public JavaTest3(int c){
        this.c = c;
    }

    public JavaTest3(){
    }

    public static void main(String[] args) {
        JavaTest3 javaTest3 = new JavaTest3(33);
        System.out.println("a:"+javaTest3.a);
        System.out.println("b:"+javaTest3.b);
        System.out.println("c:"+javaTest3.c);
    }
}

5.2.输出

a:3
b:6
c:33

5.3.结论

  1. 不管是先对成员变量进行初始化块初始化,还是先进行声明声明变量的默认初始化,都与代码的执行顺序有关系。谁放在前面谁先执行,后面执行再对其进行覆盖。
  2. 构造器是在两者后面执行的,若在构造器中再赋值变量,则会覆盖之前的初始化值。
  3. 本质上下面的1和2的方式效果是一样的。
    //1.直接声明时初始化(赋值)
    int a = 3;
    //2.先声明,后在初始化块中初始化(赋值)
    int a;
    {
        a = 3;
    }

参考:

  1. Java虚拟机之代码的编译和类加载
  2. Java类加载过程
  3. 《疯狂java讲义》

你可能感兴趣的:(java静态初始化块,初始化块和构造器关系)