先做个常见的面试题

class Parent {
    static String TAG = "Victor";
  
    static {
        System.out.println("Parent静态代码块");
    }
}

class Child extends Parent {
    static {
        System.out.println("Child静态代码块");
    }
}

public static void main(String[] args) {
    System.out.println(Child.TAG);
}

结果输出

Parent静态代码块
Victor

虽然写的代码是用子类调用父类的TAG,但是只调用了父类的静态代码块

因为对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

稍微做些改动

ParentTAG前面加个final,其余不变

class Parent {
    static final String TAG = "Victor";
  
    static {
        System.out.println("Parent静态代码块");
    }
}

结果输出

Victor

改动完以后与父类也无关了

因为加了final以后,TAG变成了常量,常量在编译阶段会存到调用的类的常量池中,所以运行的时候 main 方法调用TAG时已经和父类无关了,不会触发父类的初始化

再举一个例子

class Parent {
    static {
        System.out.println("Parent静态代码块");
    }
  
    {
        System.out.println("Parent普通代码块");
    }
  
    Parent() {
        System.out.println("Parent构造方法");
    }
}

class Child extends Parent {
    static {
        System.out.println("Child静态代码块");
    }
  
    {
        System.out.println("Child普通代码块");
    }
  
    Child() {
        System.out.println("Child构造方法");
    }
}

 public static void main(String[] args) {
     Parent child = new Child();
 }

结束输出

Parent静态代码块
Child静态代码块
Parent普通代码块
Parent构造方法
Child普通代码块
Child构造方法

再加上变量的初始化

class Example {
    private static Example example1 = new Example();
    public static int num1 = 1;
    public static int num2 = 2;
  
    private Example() {
        num1 = 100;
        num2 = 200;
    }
  
    public static Example getInstance() {
        return example1;
    }
}

public static void main(String[] args) {
    Example.getInstance();
    System.out.println(Example.num1);
    System.out.println(Example.num2);
}

结果输出

1
2

如果将private static Example example1 = new Example();位置变换一下

class Example {
    public static int num1 = 1;
    public static int num2 = 2;
    private static Example example1 = new Example();
  
    private Example() {
        num1 = 100;
        num2 = 200;
    }
  
    public static Example getInstance() {
        return example1;
    }
}

public static void main(String[] args) {
    Example.getInstance();
    System.out.println(Example.num1);
    System.out.println(Example.num2);
}

结果输出

100
200

两个结果却不一样的原因很简单,就是属性是按自上而下的顺序初始化的

例1里面执行private static Example example1 = new Example();的时候,num1num2还是原始的值0,并没有执行到赋值的语句,然后构造函数内将num1num2赋值以后new Example();算是执行完成了,接下来继续往下走,将num1赋成1,将num2赋成2

例2就很容易理解了,执行private static Example example1 = new Example();之前,num1num2就已经初始化了,后来在构造函数里更改了

同一类里的变量和代码块顺序

class Parent {
    public static int a = 100;
    
    static {
        System.out.println(a);
    }
}

public static void main(String[] args) {
    new Parent();
}

结果输出

100

如果调换顺序之后

class Parent {
    static {
        try {
            val f = Parent.class.getDeclaredField("a");
            System.out.println(f.get(null));
        } catch (Exception ignored) {
        }
    }
  
    public static int a = 100;
}

public static void main(String[] args) {
    new Parent();
}

结果输出

0

说明执行 static 代码块的时候a还没有赋值,说明执行顺序是由上到下,同理普通代码块也是一样

结论

  1. 父类静态变量、父类静态代码块 自上而下
  2. 子类静态变量、子类静态代码块 自上而下
  3. 父类普通变量、父类普通代码块 自上而下
  4. 父类构造方法
  5. 子类普通变量、子类普通代码块 自上而下
  6. 子类构造方法