蜗牛大师

吴庆龙的学习笔记

导航

构造函数、静态变量、代码块执行顺序

1. 概述

本文主要分析在创建对象实例的时候, 类中构造方法的调用顺序或类中成员变量的赋值过程.

2. 构造函数/静态代码块/普通代码块

public class Animal {

    public Animal() {
        System.out.println("Animal Constructor.");
    }

    static {
        System.out.println("Animal static code.");
    }

    {
        System.out.println("Animal normal code.");
    }

}
public class Cat extends Animal{

    public Cat() {
        System.out.println("Cat Constructor.");
    }

    static {
        System.out.println("Cat static code.");
    }

    {
        System.out.println("Cat normal code.");
    }

}
public class Dog extends Animal {

    public Dog() {
        System.out.println("Dog Constructor.");
    }

    static {
        System.out.println("Dog static code.");
    }

    {
        System.out.println("Dog normal code.");
    }

}
public class Main {

    public static void main(String[] args) {

        new Cat();
        System.out.println("-----------------");
        new Dog();

    }

}

输出结果如下

Animal static code.
Cat static code.
Animal normal code.
Animal Constructor.
Cat normal code.
Cat Constructor.
-----------------
Dog static code.
Animal normal code.
Animal Constructor.
Dog normal code.
Dog Constructor.

从输出结果发现, 在创建一个对象的时候, 会发生如下的过程

  1. 查找父类是否被加载过, 没有加载则进行加载, 这时静态代码块随之调用
  2. 加载子类, 子类静态代码块随之调用
  3. 调用父类的普通代码块
  4. 调用父类的构造方法
  5. 调用子类的普通代码块
  6. 调用子类的构造方法

注意: 在子类的构造方法中, super()必须先被调用, 如果没有写, 运行时会自动调用super()方法. 如果父类没有无参构造方法, 需要手动进行super()的调用.

总结: 在子类构造方法中, 不管是手动调用父类构造方法还是运行时自动调用, 必须满足: 1. 子类的构造方法必须先调用父类中的某个构造方法; 2. 被子类调用的父类构造方法在父类中必须是存在的.

反编译class文件, 会发现其实普通代码块的代码被合并到构造方法中了. 但是在构造方法的前面执行了.

3. 关于静态代码块和静态变量的顺序

个人建议: 所有的静态变量要定义在静态代码块的前面.

为什么呢? 因为静态变量和静态代码块的执行顺序是根据代码编写的顺序进行执行的.

看一个例子

public class Dog {

    public static int num = 10;

    static {
        System.out.println("Dog static code.");

        // num 可以用于运算输出等
        num = 20;
        System.out.println(num);
    }

}

调用Dog.num时, 正常输出20.

如果代码如下呢?

public class Dog {

    static {
        System.out.println("Dog static code.");

//        System.out.println(num); // 用于输出时会报错
        num = 5; // 这里只能进行赋值, 实际上这时num本质上还未被定义
    }

    public static int num = 10;

}

调用Dog.num时, 会输出10.

还可以这样.

public class Dog {

    public static void f() {
        System.out.println(num);
    }

    static {
        System.out.println("Dog static code.");
        f();
    }

    public static int num = 10;

}

虽然不能在静态代码块中输出num, 但是可以调用方法f()进行输出, 这时num为0(初始值), 还未被赋值.

各种数值/引用类型的初始值:

数值/引用类型 初始值
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
char 空格符
boolean false
引用类型 null

4. 总结

本文比较简单, 需要记住如下几点:

  1. 构造函数/静态代码块/普通代码块的执行顺序.
  2. 静态代码块和静态变量的执行顺序与代码位置有关系.
  3. 所有的变量都会有初始值.

posted on 2018-10-09 11:38  蜗牛大师  阅读(241)  评论(0编辑  收藏  举报