Java对象初始化过程的解析

存在以下几点:

1)  任何对象在初始化之前都必须由类加载器找到并加载到内存。如果找不到字节码文件(.class文件),后面的初始化过程都不能执行,直接报错。如果已经被加载进内存,则这一步直接略过。

2)  任何一个类在被加载到内存的时候,首先会查看其父类是否已经被加载进内存,如果存在多重父类,则从最根父类开始查找加载,知道加载到本类。

3)  任何一个类在被加载进内存之后,静态的数据成员都会被先初始化。然后执行静态代码块。

4)  在初始化一个对象之前,先在内存分配合适大小的存储空间并初始化,所以此时对象的所有域成员(数据成员)都会被初始化为默认值。

5)  初步初始化域成员之后就是将对象具有的方法的地址都存入对象。

6)  初步初始化之后,开始从上到下将所有的域成员的值更改为代码中指定的值。

7)  域成员初始化完毕之后开始执行非静态代码块。

8)  最后才是调用本类的构造方法。

所以,整个初始化过程存在很多复杂的操作,不仅是我们只看到的一个new关键字。

         类比对象先初始化,所以静态数据成员,会跟随类进行初始化。

         父类先比子类初始化。所以父类的静态成员执行完之后在执行子类的静态成员;父类的构造器执行完之后才执行子类的构造器。

         所以下面的这段代码很具有代表性:

/**
 * @author Daniel Chiu
 *
 */
public class TestConstructProcedure
{

	public static void main(String[] args)
	{
		Lunch lunch = new Lunch();
	}
}

class Meal
{
	private static int i = 1;
	private static String str = "one";

	static
	{
		System.out.println("Invoke Meal static method");
		System.out.println("i=" + i);
		System.out.println("str=" + str);
		i = 2;
		str = "two";
	}

	private int i2 = 1;
	private String str2 = "one";

	{
		System.out.println("Invoke Meal non_static method");
		System.out.println("i2=" + i2);
		System.out.println("str2=" + i2);
		i2 = 2;
		str2 = "two";
	}

	public Meal()
	{
		System.out.println("Constructer:Meal.Meal()");
		System.out.println("i=" + i);
		System.out.println("str=" + i);
		System.out.println("i2=" + i2);
		System.out.println("str2=" + i2);
		System.out.println("Test polimorphism in constructor");
		polimorphism();
	}

	public void polimorphism()
	{
		System.out.println("Meal.polimorphism()");
	}
}

class Lunch extends Meal
{
	private static int i3 = 1;
	private static String str3 = "one";

	private Bread bread = new Bread();
	private Chess chess = new Chess();

	private static Bread bread2 = new Bread();

	static
	{
		System.out.println("Invoke Lunch static method");
		System.out.println("i3=" + i3);
		System.out.println("str3=" + str3);
		i3 = 2;
		str3 = "two";
	}

	private int i4 = 1;
	private String str4 = "one";

	{
		System.out.println("Invoke Lunch non_static method");
		System.out.println("i4=" + i3);
		System.out.println("str4=" + str4);
		i4 = 2;
		str4 = "two";
	}

	public Lunch()
	{
		System.out.println("Constructer:Lunch.Lunch()");
		System.out.println("i3=" + i3);
		System.out.println("str3=" + str3);
		System.out.println("i4=" + i3);
		System.out.println("str4=" + str4);
	}

	@Override
	public void polimorphism()
	{
		System.out.println("Lunch.polimorphism()" + " i3=" + i3 + " str3=" + str3 + " i4=" + i4 + " str4=" + str4);
	}
}

class Bread
{
	private static int i5 = 1;
	private static String str5 = "one";
	static
	{
		System.out.println("Invoke Bread static method");
		System.out.println("i5=" + i5);
		System.out.println("str5=" + str5);
		i5 = 2;
		str5 = "two";
	}

	private int i6 = 1;
	private String str6 = "one";

	{
		System.out.println("Invoke Bread non_static method");
		System.out.println("i6=" + i6);
		System.out.println("str6=" + str6);
		i6 = 2;
		str6 = "two";
	}

	public Bread()
	{
		System.out.println("Constructer:Bread.Bread()");
		System.out.println("i5=" + i5);
		System.out.println("str5=" + str5);
		System.out.println("i6=" + i6);
		System.out.println("str6=" + str6);
	}

}

class Chess
{
	private static int i5 = 1;
	private static String str5 = "one";
	static
	{
		System.out.println("Invoke Chess static method");
		System.out.println("i5=" + i5);
		System.out.println("str5=" + str5);
		i5 = 2;
		str5 = "two";
	}

	private int i6 = 1;
	private String str6 = "one";

	{
		System.out.println("Invoke Chess non_static method");
		System.out.println("i6=" + i6);
		System.out.println("str6=" + str6);
		i6 = 2;
		str6 = "two";
	}

	public Chess()
	{
		System.out.println("Constructer:Chess.Chess()");
		System.out.println("i5=" + i5);
		System.out.println("str5=" + str5);
		System.out.println("i6=" + i6);
		System.out.println("str6=" + str6);
	}
}
//output
Invoke Meal static method
i=1
str=one
Invoke Bread static method
i5=1
str5=one
Invoke Bread non_static method
i6=1
str6=one
Constructer:Bread.Bread()
i5=2
str5=two
i6=2
str6=two
Invoke Lunch static method
i3=1
str3=one
Invoke Meal non_static method
i2=1
str2=1
Constructer:Meal.Meal()
i=2
str=2
i2=2
str2=2
Test polimorphism in constructor
<span style="background-color: rgb(51, 204, 255);">Lunch.polimorphism() i3=2 str3=two i4=0 str4=null</span>
Invoke Bread non_static method
i6=1
str6=one
Constructer:Bread.Bread()
i5=2
str5=two
i6=2
str6=two
Invoke Chess static method
i5=1
str5=one
Invoke Chess non_static method
i6=1
str6=one
Constructer:Chess.Chess()
i5=2
str5=two
i6=2
str6=two
Invoke Lunch non_static method
i4=2
str4=one
Constructer:Lunch.Lunch()
i3=2
str3=two
i4=2
str4=two

 

分析上面的对象初始化过程:

1)  找到Lunch的类文件,发现其存在父类Meal,所以先去加载Meal.class文件。

2)  加载进Meal.class文件,发现其存在静态成员,则先去初始化静态成员。所以此时i1=1,str=one.

3)  初始化静态成员之后又发现存在静态代码块,所以执行静态代码块,此时i1=2,str=two.到此父类class文件加载完成。

4)  回到子类,准备加载子类的class文件,发现存在静态Bread对象,则先去加载Bread.class。

5)  加载Bread类文件同样的先看有没有父类,如果有则和上面的一样。这里没有但是发现Bread.class也存在静态成员和静态代码块,则采用和Meal一样的步骤进行类级初始化。

6)  加载完Bread之后又发现它直接就使用了new关键字,就又回去进行对象初始化工作,先执行非静态代码块,再执行构造函数,

7)  初始化完Bread对象,又回到Lunch,这时没有继续同样的加载Chess,而是加载Lunch本身的类文件。同样的执行静态代码块。

8)  Lunch.class加载完成,开始执行lunch对象的初始化工作,这时因为Lunch存在父类,所以必须调用父类的一套初始化方案将从父类继承来的数据成员初始化,所以调用了父类的非静态代码块,之后再是构造函数。

9)  从父类继承来的数据成员通过父类初始化方案进行了初始化。现在开始Lunch自身新加的数据成员的初始化。发现存在Bread对象和Chess对象,但是之前已经加载了Bread的类文件,先执行Bread对象初始化,所以调用Bread的非静态代码块和构造方法。之后就是Chess的类文件加载和初始化。

10)将Bread和Chess对象初始化之后,开始执行自己的非静态代码块和构造方法。

    上面的代码还有一点在初始化一个对象的数据成员时,如果发现不是基本类型,那又得开始重新加载类的过程。

    你会发现如下特点:

1)  类文件总是最先加载。

2)  加载类文件的时候会跟着将静态成员初始化,如果存在静态代码块也会跟着执行。

3)  将类中所涉及的类(父类,静态成员涉及到的类,或者静态代码用到的类)都加载完成之后才开始对象的初始化工作(这个涉及到的类并不包括非静态成员涉及)。

4)  开始对象的初始化之前必须先要在内存中开辟一块相应的空间,并将涉及的非静态数据成员置为默认值(0或null)。

5)  对象初始化同样遵守先初始化从父类继承来的非静态成员,而父类的这些成员使用的就是从父类继承而来的初始化代码。所以如果此时通过某种途径访问子类中的数据成员应该都是默认值。(比如父类构造器中进行多态,上面用颜色标出)

6)  父类初始化完成的最后一步就是调用父类继承来的构造器。

7)  从父类继承而来的数据成员初始化之后,开始初始化子类新增的数据成员,先将子类的数据成员的值按照代码的指示设置。如果此时子类存在组合的情况,那么依据上面所说的步骤初始化这个组合成员。

8)  当所有的数据成员都设置完毕了之后执行非静态代码块。

9)  对象初始化的最后一步就是调用子类自身的构造器。

10)父类和子类的构造器看起来是一次执行的,但实际上之间和隔着好几种操作。

        继续终极总结:对象的初始化就是下面这个顺序的嵌套

        加载类初始化(静态成员和执行静态代码)-->开辟内存空间(顺便将所有的非静态成员置为默认值)-->将非静态成员设置为代码中为其指定的值-->调用非静态代码块调用构造方法

并且遵守以下几个原则:

1)  先父类后子类

2)  先静态后非静态

3)  如果是同级的(如同为静态成员或数据成员)则按照书写的顺序执行

        有一点必须要明确:虽然一个对象在初始化期间不断在调用父类的方法和构造方法,但是这些方法是属于子类的,它是从父类继承下来的。只是初始化从父类的继承下来的数据成员必须通过父类的方式,并且必须先初始化这些从父类继承来的数据成员,所以才出现了这种先调用父类后子类的表象。实际上子类中并没有含有父类的对象,它只是含有从父类继承下来的这些结构。这一点必须要弄清楚。

       对象的初始化过程非常复杂,设计很多操作。了解对象的初始化过程对于写出好的代码是很有帮助的。同时,由于Java的垃圾回收机制,使得程序员从清理对象的海洋中释放出来。垃圾回收器一旦发现某个对象的引用没有被任何对象持有,就会在合适的时候回收对象。所以这里面很重要的一点,我们不能确定垃圾回收器到底什么时候进行回收操作。

      虽然对于对象的释放,垃圾回收器帮我们完成了。但是如果一个对象代表的是一项重要资源。比如输入输出流,那我们必须在使用完之后手动进行关闭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值