1. 对象创建的过程

简要总结, 详细见 2. 类的初始化顺序.

  1. 永久区中检查要创建的对象所属的类的类模板信息(类的数据结构);
  2. 如果类模板不存在,则由类加载器负责加载类模板;
  3. 如果类模板已经存在了,不加载,保证类模板只有一份;
  4. 依据类模板中所有属性的定义信息(修饰符,数据类型,属性名…)在 GC 区中开辟一块适当的空间;
  5. 把此空间全部写 0,目的就是让所有属性自动拥有缺省值 0
  6. 属性是否有显式赋值,有则执行;
  7. 执行构造方法;
  8. 把对象在堆中的地址返回给创建者。


类的创建过程 - 图1

2. 类的初始化顺序

静态代码块:用 staitc 声明,jvm 加载类时执行,仅执行一次
构造代码块:类中直接用 {} 定义,每一次创建对象时执行。
执行顺序优先级:main() 所在类的静态代码块, main(), 紧接着才是创建对象的顺序.

2.1 对于一个类的情况

例 1:

  1. public class HelloA {
  2. public HelloA(){//构造函数
  3. System.out.println("A的构造函数");
  4. }
  5. {//构造代码块
  6. System.out.println("A的构造代码块");
  7. }
  8. static {//静态代码块
  9. System.out.println("A的静态代码块");
  10. }
  11. public static void main(String[] args) {
  12. }
  13. }

运行结果:

A的静态代码块

例 2:

public class HelloA {
    public HelloA(){//构造函数
        System.out.println("A的构造函数");    
    }
    {//构造代码块
        System.out.println("A的构造代码块");    
    }
    static {//静态代码块
        System.out.println("A的静态代码块");        
    }
    public static void main(String[] args) {
        HelloA a=new HelloA();    
    }
}

运行结果:

A的静态代码块
A的构造代码块
A的构造函数

例 3:

public class HelloA {
    public HelloA(){//构造函数
        System.out.println("A的构造函数");    
    }
    {//构造代码块
        System.out.println("A的构造代码块");    
    }
    static {//静态代码块
        System.out.println("A的静态代码块");        
    }
    public static void main(String[] args) {
        HelloA a=new HelloA();
        HelloA b=new HelloA();
    }
}

运行结果:

A的静态代码块
A的构造代码块
A的构造函数
A的构造代码块
A的构造函数

对于一个类而言,按照如下顺序执行:

  1. 执行静态代码块
  2. 执行构造代码块
  3. 执行构造函数

对于静态变量静态代码化块变量构造代码块构造器,它们的初始化顺序依次是(静态变量、静态代码块)>(变量、构造代码块)> 构造器, 见下例.
例子 4:

public class InitialOrderTest {
         /* 静态变量 */
     public static String staticField = "静态变量";
         /* 变量 */
     public String field = "变量";
         /* 静态初始化块 */
     static {
         System.out.println( staticField );
         System.out.println( "静态代码" );
     }
         /* 初始化块 */
     {
         System.out.println( field );
         System.out.println( "构造代码块" );
     }
         /* 构造器 */
     public InitialOrderTest(){
         System.out.println( "构造器" );
     }

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

运行结果:

静态变量
静态代码块
变量
构造代码块
构造器

2.2 对于继承情况

例子 5:

public class HelloA {
    public HelloA(){//构造函数
        System.out.println("A的构造函数");    
    }
    {//构造代码块
        System.out.println("A的构造代码块");    
    }
    static {//静态代码块
        System.out.println("A的静态代码块");        
    }
}
public class HelloB extends HelloA{
    public HelloB(){//构造函数
        System.out.println("B的构造函数");    
    }
    {//构造代码块
        System.out.println("B的构造代码块");    
    }
    static {//静态代码块
        System.out.println("B的静态代码块");        
    }
    public static void main(String[] args) {
        HelloB b=new HelloB();        
    }
}

运行结果:

A的静态代码块
B的静态代码块
A的构造代码块
A的构造函数
B的构造代码块
B的构造函数

当涉及到继承时,按照如下顺序执行:

  1. 执行父类的静态代码块,并初始化父类静态成员变量
  2. 执行子类的静态代码块,并初始化子类静态成员变量
  3. 执行父类的构造代码块,执行父类的构造函数,并初始化父类普通成员变量
  4. 执行子类的构造代码块, 执行子类的构造函数,并初始化子类普通成员变量

Java 初始化顺序图:类的创建过程 - 图2

例子 6:

class Parent {
        /* 静态变量 */
    public static String p_StaticField = "父类--静态变量";
         /* 变量 */
    public String    p_Field = "父类--变量";
    protected int    i    = 9;
    protected int    j    = 0;
        /* 静态初始化块 */
    static {
        System.out.println( p_StaticField );
        System.out.println( "父类--静态初始化块" );
    }
        /* 初始化块 */
    {
        System.out.println( p_Field );
        System.out.println( "父类--初始化块" );
    }
        /* 构造器 */
    public Parent()
    {
        System.out.println( "父类--构造器" );
        System.out.println( "i=" + i + ", j=" + j );
        j = 20;
    }
}

public class SubClass extends Parent {
         /* 静态变量 */
    public static String s_StaticField = "子类--静态变量";
         /* 变量 */
    public String s_Field = "子类--变量";
        /* 静态初始化块 */
    static {
        System.out.println( s_StaticField );
        System.out.println( "子类--静态初始化块" );
    }
       /* 初始化块 */
    {
        System.out.println( s_Field );
        System.out.println( "子类--初始化块" );
    }
       /* 构造器 */
    public SubClass()
    {
        System.out.println( "子类--构造器" );
        System.out.println( "i=" + i + ",j=" + j );
    }


        /* 程序入口 */
    public static void main( String[] args )
    {
        System.out.println( "子类main方法" );
        new SubClass();
    }
}

运行结果:

父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
子类main方法
父类--变量
父类--初始化块
父类--构造器
i=9, j=0
子类--变量
子类--初始化块
子类--构造器
i=9,j=20

子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。静态变量、静态初始化块,变量、初始化块初始化了顺序取决于它们在类中出现的先后顺序。

4.3 总结

① 访问 SubClass.main(),(这是一个 static 方法),于是装载器就会为你寻找已经编译的 SubClass 类的代码(也就是 SubClass.class 文件)。在装载的过程中,装载器注意到它有一个基类(也就是 extends 所要表示的意思),于是它再装载基类。不管你创不创建基类对象,这个过程总会发生。如果基类还有基类,那么第二个基类也会被装载,依此类推。

② 执行根基类的 static 初始化,然后是下一个派生类的 static 初始化,依此类推。这个顺序非常重要,因为派生类的“static初始化”有可能要依赖基类成员的正确初始化。

③ 当所有必要的类都已经装载结束,开始执行 main() 方法体,并用 new SubClass(...) 创建对象。

④ 根据 new SubClass(...) 选择子类中相应的构造器, 子类构造器首行分以下情况:

  • , 则强制添加 super();
  • super(), 则执行父类无参构造器, 父类无无参构造器则报错;
  • super(...), 则执行父类相应有参构造器, 父类无则报错;
  • this(...), 则调用子类本类重载构造, 而调用重载构造又会回到开头, 由于总会有一个构造器无this(...), 则由此构造器根据其 super(...) 调用相应父类构造器.

⑤ 对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分.