java学习面向对象之父子构造函数初始化

在之前讲到java面向对象继承的时候,我们只讲到了两个比较重要的知识点,一个是父子类当中有同名的成员变量,这个时候,我们引入了super这个关键字来区分这两个同名成员变量,除此之外,我们还讲到了父子同名函数的覆盖,这父子同名函数必须是参数类型、个数相同,返回值也相同才可以,同时子类要覆盖后的成员方法的访问权限要大于等于父类当中的同名函数权限。

那么还有一个地方我们没有讲到,那就是父子当中的构造函数是怎样的?

class Fu
{

	Fu()
	{

		System.out.println("Fu chu shihua ");

	}


}

class Zi extends Fu
{

	Zi()
	{

		System.out.println("Zi lei chushi hua ");

	}

}

class Extend1
{

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

	}

}

此时我们编译,运行后结果是:

通过这个结果我们可以看出,即使我们初始化子类的时候,并没有显式的调用父类的构造函数,但是子类当中还是隐式的调用了父类的构造函数。那么把这个隐式调用转换成显式的是如何的呢?在我们子类的构造函数在JVM解析的时候默认加了一个super(),这个函数。这个函数是不是跟我们之前讲到构造函数的时候的this()调用本地构造方法很相似呢?

class Zi extends Fu
{

	Zi()
	{
		super();//这里就隐式调用了父类的构造函数
		System.out.println("Zi lei chushi hua ");

	}

}

那么这里的隐式调用有一个不足,什么不足呢,如果我们此时父类的构造函数接受的参数不是空,这个时候在隐式调用的时候就会出错。因为隐式调用super()并没有接收参数,跟父类的构造函数不匹配,自然而然的会报错。

 

那么在这里还要讨论一下为什么要有super这个关键字,为什么子类的构造函数会隐式的调用父类当中的调用方法呢?

比如现在父类当中有一个成员变量,当我们要使用这个成员变量的时候,构造函数会对其进行初始化,如果此时我们如果调用未初始化的成员变量,那么此时变量就是在堆中默认的变量,这样显然是不合适的,调用一个未初始化的变量,毫无意义可言。

也就是说子类继承了父类中的内容(属性)在子类使用父类的属性之前,必须了解父类是如何对其属性进行初始化操作的。为完成这个初始化操作,子类必须在构造函数当中访问这个父类的构造函数super()。

class Fu
{

	int num;
	Fu( int x)
	{
		num = x;
		System.out.println("Fu chu shihua ");

	}


}

class Zi extends Fu
{

	Zi()
	{
		super(8);
		System.out.println("Zi lei chushi hua "+num);

	}

}

class Extend1
{

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

	}

}

比如这里如果我们不调用super()这个方法的时候,就会显得没有意义可言。

这里还有需要注意的一点就是,如果构造函数当中既有this() 还有 super()这两个构造函数的时候,我们应当如何处理。显然这两个方法不能存在一个构造函数体当中,因为这个两个构造方法,必须放到构造函数的第一个位置。既然调用this()就证明此时我们有多个构造函数对于一个类,这个时候我们可以把super()放到别的不用this()这个构造方法的类当中,代码如下:

class Fu
{

	int num;
	Fu( int x)
	{
		num = x;
		System.out.println("Fu chu shihua ");

	}


}

class Zi extends Fu
{

	Zi(int x)
	{
		super(9);
		System.out.println("This is first");

	}

	Zi()
	{
		this(9);
		System.out.println("Zi lei chushi hua "+num);

	}

}

class Extend1
{

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

	}

}

Java当中的所有类都默认继承字object类,并且这个类在java虚拟机一运行的时候,就存在着。

那么java在调用构造函数时的内存图是怎样的呢?我们来画一下:

class Fu
{

	Fu()
	{

		show();

	}

	void show()
	{

		System.out.println("Fu class construct");

	}


}

class Zi extends Fu
{

	int num = 10;

	Zi()
	{

		super();

	}

	void show()
	{

		System.out.println("Zi lei construct"+num);

	}

}

class Extend1
{

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

	}

}

我们编译运行之后,结果就是:

 

我们来修改一下代码如下:

class Fu
{

	Fu()
	{

		show();

	}

	void show()
	{

		System.out.println("Fu class construct");

	}


}

class Zi extends Fu
{

	int num = 10;

	Zi()
	{

		super();

	}

	void show()
	{

		System.out.println("Zi lei construct"+num);

	}

}

class Extend1
{

	public static void main(String[] args) {
		
		Zi z = new Zi();
		z.show();
	}

}

这个时候输出的结果是:

那么综合以上两个结果还有代码不同外,我们会有下面两个问题:

1、我们在只new Zi()的时候,为什么num是0

2、在我们调用show()方法的时候,为什么父类反而调用了子类的成员方法?

对于第二个问题,我们上一节讲到方法覆盖的时候就已经讲到过了,当子类的方法和父类的方法相同时,调用的是子类的方法。

对于第一个问题,我们用一个内存图解一下:

以上就是父子类构造方法调用的内存示意图。

需要说明的有两点:

1、通过super()初始化父类的时候,子类的成员变量并未初始化。等父类初始化完毕,才进行子类的成员变量显示初始化。

一个对象的实例化过程:

一、JVM读取指定目录下的.class文件,并且加载进内存。并且先加载此类的父类(在有直接父类的情况下)

二、在堆内存当中开辟内存,分配地址。

三、并在对象的空间内,对空间属性进行默认初始化

四、调用对应的构造函数进行初始化

五、在构造函数当中,第一行会先调用父类的构造函数进行初始化。

六、父类初始化完毕后,再对子类的属性进行显式初始化

七、再对子类的构造函数进行特定初始化

八、初始化完毕后,把地址赋值给引用变量。

待续....

posted @ 2013-09-25 21:18  stark_javac  阅读(1224)  评论(0编辑  收藏  举报