1. 对象创建的过程
简要总结, 详细见 2. 类的初始化顺序.
- 在永久区中检查要创建的对象所属的类的类模板信息(类的数据结构);
- 如果类模板不存在,则由类加载器负责加载类模板;
- 如果类模板已经存在了,不加载,保证类模板只有一份;
- 依据类模板中所有属性的定义信息(修饰符,数据类型,属性名…)在
GC
区中开辟一块适当的空间; - 把此空间全部写
0
,目的就是让所有属性自动拥有缺省值0
; - 属性是否有显式赋值,有则执行;
- 执行构造方法;
- 把对象在堆中的地址返回给创建者。
2. 类的初始化顺序
静态代码块:用 staitc
声明,jvm 加载类时执行,仅执行一次
构造代码块:类中直接用 {}
定义,每一次创建对象时执行。
执行顺序优先级:main()
所在类的静态代码块, main()
, 紧接着才是创建对象的顺序.
2.1 对于一个类的情况
例 1:
public class HelloA {
public HelloA(){//构造函数
System.out.println("A的构造函数");
}
{//构造代码块
System.out.println("A的构造代码块");
}
static {//静态代码块
System.out.println("A的静态代码块");
}
public static void main(String[] args) {
}
}
运行结果:
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的构造函数
对于一个类而言,按照如下顺序执行:
- 执行静态代码块
- 执行构造代码块
- 执行构造函数
对于静态变量、静态代码化块、变量、构造代码块、构造器,它们的初始化顺序依次是(静态变量、静态代码块)>(变量、构造代码块)> 构造器
, 见下例.
例子 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的构造函数
当涉及到继承时,按照如下顺序执行:
- 执行父类的静态代码块,并初始化父类静态成员变量
- 执行子类的静态代码块,并初始化子类静态成员变量
- 执行父类的构造代码块,执行父类的构造函数,并初始化父类普通成员变量
- 执行子类的构造代码块, 执行子类的构造函数,并初始化子类普通成员变量
Java 初始化顺序图:
例子 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(...)
调用相应父类构造器.
⑤ 对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分.