初始化顺序
初始化顺序
Java 虚拟机通过装载、连接、初始化来使得一个 Java 类型可以被 Java 程序所使用,如下图所示,其中连接过程又分为验证、准备、解析三个部分。其中部分类的解析过程可以推迟到程序真正使用其某个符号引用时再去解析。
静态初始化
主动使用会导致类的初始化,其超类均将在该类的初始化之前被初始化,但通过子类访问父类的静态字段或方法时,对于子类(或子接口、接口的实现类)来说,这种访问就是被动访问,或者说访问了该类(接口)中的不在该类(接口)中声明的静态成员。
- 无论直接通过 new 创建出来的,还是通过反射、克隆、序列化创建的)创建某个类新的实例
- 使用某个类的静态方法
- 访问某个类或接口的静态字段
- 调用 JavaAPI 中的某些反射方法
- 初始化某个类的子类(要求其祖先类都要被初始化,否则无法正确访问其继承的成员)
- 启动某个标明为启动类的类(含有 main()方法)
下面我们用例子来阐述下类初始化的流程:
public class GrandPa {
static {
System.out.println("祖父静态初始化");
}
}
public class Parent extends GrandPa {
static String language = "Chinese";
static {
System.out.println("父类静态初始化");
}
{
System.out.println("父类实例初始化");
}
Parent() {
System.out.println("父类构造函数");
}
}
public class Child extends Parent {
static {
System.out.println("子类静态初始化");
}
{
System.out.println("子类实例初始化");
}
Child() {
System.out.println("子类构造函数");
}
public static void main(String args[]) {
Child child = new Child();
}
}
public class PassiveAccessTest {
public static void main(String[] args) {
System.out.println(Child.language);
}
}
//输出结果
/*
祖父静态初始化
父类静态初始化
Chinese
*/
而如果我们在Child
类中访问本类继承自父类的静态变量,会得到如下顺序:
祖父静态初始化
父类静态初始化
子类静态初始化
Chinese
实例初始化
类的初始化会从祖先类到子类、按出现顺序,对类变量的初始化语句、静态初始化语句块依次进行初始化。而对类实例的初始化也类似,会从祖先类到子类、按出现顺序,对类成员的初始化语句、实例初始化块、构造方法依次进行初始化。
当我们在子类中初始化子类时,即:
public static void main(String args[]) {
Child child = new Child();
}
其输出效果为:
祖父静态初始化
父类静态初始化
子类静态初始化
父类实例初始化
父类构造函数
子类实例初始化
子类构造函数
而如果我们为 Child
添加如下属性:
static Child child = new Child();
那么此时实例构造过程会被提前到静态初始化之前,其输出为:
祖父静态初始化
父类静态初始化
父类实例初始化 // 此时父类实例初始化在子类静态初始化之前了
父类构造函数
子类实例初始化
子类构造函数
子类静态初始化
父类实例初始化
父类构造函数
子类实例初始化
子类构造函数
下面看一个复杂点的例子:
class ComplexInitialization implements Cloneable {
public static int k = 0;
public static ComplexInitialization t1 = new ComplexInitialization("t1");
public static ComplexInitialization t2 = new ComplexInitialization("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
{
print("构造块");
}
static {
print("静态块");
}
public ComplexInitialization(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
++i;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String[] args) {
ComplexInitialization t = new ComplexInitialization("init");
}
}
其输出为:
首先T类被加载、连接后进行初始化,会先对字段k、t1、t2、i、n以及static块进行初始化。
1:j i=0 n=0
t1实例的初始化会初始化实例成员j,(实际上先进行父类实例内容的初始化)先调用静态方法print,并执行实例初始化块{},输出
2:构造块 i=1 n=1
随后调用t1实例的构造函数,输出:
3:t1 i=2 n=2
类似有t2实例的初始化:
4:j i=3 n=3
构造块 i=4 n=4
5:构造块 i=4 n=4
6:t2 i=5 n=5
i的初始化:
7:i i=6 n=6
n的初始化和静态块的初始化:
8:静态块 i=7 n=99
t实例的初始化:
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102
构造函数顺序
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
// SubClass 类继承
class SubClass extends SuperClass{
private int n;
SubClass(){ // 自动调用父类的无参数构造器
System.out.println("SubClass");
}
public SubClass(int n){
super(300); // 调用父类中带有参数的构造器
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
// SubClass2 类继承
class SubClass2 extends SuperClass{
private int n;
SubClass2(){
super(300); // 调用父类中带有参数的构造器
System.out.println("SubClass2");
}
public SubClass2(int n){ // 自动调用父类的无参数构造器
System.out.println("SubClass2(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
System.out.println("------SubClass 类继承------");
SubClass sc1 = new SubClass();
SubClass sc2 = new SubClass(100);
System.out.println("------SubClass2 类继承------");
SubClass2 sc3 = new SubClass2();
SubClass2 sc4 = new SubClass2(200);
}
}
/**
------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200
**/