构造器的调用顺序
基类的构造器总是在子类的构造过程中被调用,而且按照继承层次逐渐向上连接,所以每个基类的构造器都能得到调用。
这是为什么呢?
构造器的一项任务就是检查对象是否被正确构造.子类只能访问自己的成员,不能访问基类的成员(基类成员大多数是private类型)。只有基类的构造器才能具有恰当的知识和权限来对自己的元素进行初始化。因此必须令所有构造器都得到调用,否则就不可能正确的构造完整对象。
因此在子类的构造器主体中,如果没有明确指定调用摸个基类构造器,就会默认调用默认构造器。如果不存在默认构造器就会报错
如下代码所示
package extend; class Meal{ Meal(){ System.out.println("Meal()"); } } class Bread{ Bread(){ System.out.println("Bread()"); } } class Cheese{ Cheese(){ System.out.println("Cheese()"); } } class Lettuce{ Lettuce(){ System.out.println("Lettcue()"); } } class Lunch extends Meal{ Lunch(){ System.out.println("Lunch()"); } } class PortableLunch extends Lunch{ PortableLunch(){ System.out.println("PortableLunch()"); } } public class Main extends PortableLunch { private Bread b=new Bread(); private Cheese c=new Cheese(); private Lettuce l=new Lettuce(); public Main(){ System.out.println("Main()"); } public static void main(String[] args) { // TODO Auto-generated method stub new Main(); } }
这段代码的输出为:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettcue()
Main()
从这个可以看出,main中先调用了Main的构造器,但是Main是继承自Meal,Lunch,PortableLunch的。由于基类的构造器总是在子类的构造过程中调用,所以构造器的调用过程为Meal(),Lunch(),PortableLunch().在构造某个类时首先要按照声明顺序调用成员的初始化方法,接下来是调用子类的构造器主体所以在Main这个类中按照声明顺序首先是Bread,接下来是Cheese以及Lettcue,最后调用构造器主体。
1. 调用基类构造器.这个步骤一直递归下去直到没有基类
2. 按照声明顺序调用成员的初始化方法
3. 调用子类构造器的主体
但是这个并不完整
下面的例子可以说明:
package extend; class Glyph{ void draw(){ System.out.println("Glphy.draw()"); } Glyph(){ System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph{ private int radius=1; RoundGlyph(int r){ radius=r; System.out.println("RoundGlyph。RoundGlyph(),radius="+radius); } void draw(){ System.out.println("RoundGlyph。draw(),radius="+radius); } } public class Main{ public static void main(String[] args) { // TODO Auto-generated method stub new RoundGlyph(5); } }
不说其他,先贴出结果
Glyph() before draw()
RoundGlyph。draw(),radius=0
Glyph() after draw()
RoundGlyph。RoundGlyph(),radius=5
从结果可以看出,main中调用了RoundGlyph的构造方法后,首先要调用基类的构造方法,所以先调用Glyph()方法,输出Glyph() before draw()
接下来调用的draw()方法,但是这里输出的是RoundGlyph。draw(),radius=0
显然这里调用的是子类的draw方法。
这个问题先不着急,然而这里的radius参数竟然是0,这说明了radius已经被初始化了,而radius是子类的对象,所以说这里有个问题就是:在main中使用new RoundGlyph()构造的时候虽然首先调用的基类的构造方法,但是已经把子类的所有对象进行默认初始化了。
调用完draw方法后接着结果就是Glyph() after draw()
到了这儿说明基类已经构造完毕,接下来就是按照声明顺序构造子类了,这个时候先是给radius赋值为1,到了构造器主体是赋值为5,然后打印出来。
通过这个例子说明了什么?
构造器的调用原理实际上是这样
在其他任何事情没有发生前,首先将分配给对象的存储空间初始化为0.这里就是将radius初始化为0.
如上面一样调用基类构造器
按照声明顺序调用成员的初识化方法
调用导出类的构造器主体。
下面解决刚才那里为什么调用的子类的draw方法?
方法绑定
将一个方法调用同一个方法的主体关联起来被称为绑定,这里是将类的对象的构造和类中方法的调用关联在一起。在Java中方法绑定采用的是后期绑定,它的含义就是运行时根据对象的类型进行绑定。
如果要调用构造器内部的动态绑定方法就要用到那个方法被覆盖后的定义。这是为什么呢?
私以为是在这个时候编译器默认的对象是RoundGlyph因为这是通过new RoundGlyph创建对象,最终还是要回到这个构造器的,那么根据运行时对象类型绑定的话,绑定的就是RoundGlyph类型,从而调用该类的draw方法,但是这只是绑定方法,其中的方法中的参数还没有进行赋值,只是默认初始化,所以就会出现radius为0的尴尬情况。
说白了在构造对象的时候还是不要调用内部方法,因为这要调用子类的覆盖的同名方法,如果要用到参数的话,子类又没有被初始化,所以参数就会出错。