Java类的加载顺序

Java类的加载顺序

先放代码:

public class Animal {

    private int i = test();

    private static int j  = method();

    static {
        System.out.println("a");
    }

    Animal(){
        System.out.println("b");
    }

    {
        System.out.println("c");
    }

    public int test(){
        System.out.println("d");
        return 1;
    }

    public static int method(){
        System.out.println("e");
        return 1;
    }
}
public class Dog extends Animal{

    {
        System.out.println("h");
    }

    private int i = test();

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

    private static int j  = method();

    Dog(){
        System.out.println("g");
    }

    @Override
    public int test(){
        System.out.println("i");
        return 1;
    }

    public static int method(){
        System.out.println("j");
        return 1;
    }
}
public static void main( String[] args )
{
    Dog dog = new Dog();
    System.out.println("---");
    Dog dog1 = new Dog();
}

执行这段main程序,会输出什么?

e
a
f
j
i
c
b
h
i
g
---
i
c
b
h
i
g

Q: 什么时候会进行静态变量的赋值和静态代码块的执行?

  • 第一次创建某个类或者某个类的子类的实例
  • 访问类的静态变量、调用类的静态方法
  • 使用反射方法forName
  • 调用主类的main方法

类初始化只会进行一次, 上面任何一种情况触发后,之后都不会再引起类初始化操作。

Q:初始化某个子类时,也会对父类做静态初始化吗?顺序呢?

如果父类之前没有被静态初始化过,那就会进行, 且顺序是先父类再子类。 后面的非静态成员初始化也是如此。

所以会先输出eafj

Q: 为什么父类的method不会被子类的method重写?

静态方法是类方法,不会被子类重写。毕竟类方法调用时,是必定带上类名的。

Q: 为什么第一个输出的是e而不是a?

因为类变量的显示赋值代码和静态代码块代码按照从上到下的顺序执行。
Animal 的静态初始化过程中,method 的调用在 static 代码块之前,所以先输出e 再输出 a。
而 Dog 的静态初始化过程中,method 的调用在 static 代码块之后,因此先输出f,再输出 j。

Q: 没有在子类的构造器中调用super()时,也会进行父类对象的实例化吗?

会的。会自动调用父类的默认构造器。 super()主要是用于需要调用父类的特殊构造器的情况。

因此会先进行Animal的对象实例化,再进行Dog的对象实例化。

Q: 构造方法、成员显示赋值、非静态代码块(即输出c和h的那2句)的顺序是什么?

  1. 成员显示赋值、非静态代码块(按定义顺序)
  2. 构造方法

因此 Animal 的实例化过程输出 icb(如果对输出i有疑问,见下面一题)
接着进行 Dog 的实例化,输出 hig。

Q: 为什么Animal实例化时, i=test()中输出的是i而不是d?

因为你真正创建的是 Dog 子类,Dog 子类中的test()方法由于签名和父类test 方法一致,因此 test 方法被重写了。

此时即使在父类中调用,也还是用使用子类 Dog 的方法。除非你 new 的是Animal。

Q: 同上题, 如果test方法都是private或者final属性, 那么上题的情况会有变化吗?

因为 private 和 final 方法是不能被子类重写的。 所以 Animal 实例化时,i=test输出 d。

总结一下顺序:

  1. 父类静态变量显式赋值、父类静态代码块(按定义顺序)
  2. 子类静态变量显式赋值、子类静态代码块(按定义顺序)
  3. 父类非静态变量显式赋值(父类实例成员变量)、父类非静态代码块(按定义顺序)
  4. 父类构造函数
  5. 子类非静态变量(子类实例成员变量)、子类非静态代码块(按定义顺序)
  6. 子类构造函数。

本文来源:

一次性搞清Java中的类加载问题 - 知乎 (zhihu.com)