关于Java构造类与对象的思考
简单记录一下Java构造类与对象时的流程以及this和super对于特殊例子的分析。
首先,接着昨天的问题,我做出了几个变形:
Pic1.原版:
Pic2.去掉了T.foo方法中的this关键字:
Pic3.在原版的基础上抹掉了B.foo方法:
Pic4.抹掉了原版的B.foo方法,同时去掉了T.foo方法中的this关键字:
这四个图全方位地对this和super两个关键字的有无进行了测试,测试结果表明:这些版本的程序的运行结果都一样...
在这里,还是插一张流程图:
首先说一下super关键字,这些代码在使用super关键字时都是这样使用的:在B.foo中调用super.foo。
但事实是,如果B中没有foo方法,其效果和B.foo中只执行一句super.foo效果是一样的。
我们可以这样去想:super.foo是将B.foo执行过程“扔到”了其父类(super)中去执行,但是B如果没有显示声明并实现foo方法,其默认行为依旧是调用B的父类的foo方法。
所以... 在这里,B类中的“void foo { super.foo(); } ”语句,是没有任何意义的一个语句(如果我是一个足够聪明的Java编译器,我是不会理这个语句的,我只需要copy一下其父类对于这个方法的实现即可,至少在这个程序中是这样)。
到这里,上述的四段不同代码因此变成了两种不同代码...
根据程序输出结果初步判断:这里this关键字的有无,对程序最终的结果没有影响。
原因分析起来也简单,尽管有个super关键字(没有super关键字的情况分析起来更容易)让我们跳到了T中去寻找代码,但当前的对象依旧是已经实例化了的B类的对象,并且此时B类已经实现了bar方法,所以当程序调用bar方法时,自然会先从B类中去寻找bar方法的实现,找到了,便直接输出“B.bar”(因此我们甚至可以这样去理解:其实每个方法前,都隐含着一个this关键字,如方法method()其实就是this.method(),事实表明,这也很可能就是Java中实现方法的机制)。
那么如何让程序经过小改动,在不改变T和B的继承关系的前提下,使得输出变为“T.bar”呢?
上述分析使得我们对于this关键字已经不抱有任何期望了。顺着上面的分析,既然要找到一个T.bar方法的实现,那直接把B中的bar方法注释掉即可。
当然,另一种思路就是:既然要输出T.bar,这个方法是T类的对象能够做到的,那么在B类中弄一个T类对象即可,就像这样(如图):
这就是两种比较简单的改动代码的方式(可能有人会觉得第二种方法或多或少不太合适,毕竟此时程序的输出实质上是靠B类中一个T类实例对象来帮忙搞定的)。
下面简单说一下类和对象构造的过程。
为了更普遍地表示类的构造过程,我画了一张这样的图:
这张图中左侧每个类之间的箭头,都表示extends,其方向由子类指向父类,每个类实现的方法,同这个类的颜色相同。
当然,图中每个子类中的从父类继承的方法,要么原封不动地保留下来,要么是覆盖掉父类原有的具体实现。我在这张图中并没有画出覆盖掉父类方法的例子(至少每种方法的颜色从始至终未曾变化),但这种情况实际上是很普遍的,这种情况画出来的话自然也会让这张图更加“多姿多彩”。
此时,我们不妨这样想一下,Java中的继承机制为单继承,但Java支持接口扩展,同时加上每个类中还含有成员变量(或者称之为类的属性),当这样一张庞大的图用上述采用不同颜色画图的方式来描述时,许许多多不同的实例对象定会五彩斑斓,这将很容易让我们理解“什么是Java中的多态”。
继续对上述的知识做个总结:在构造器实例化对象的过程中,是按照从父类到子类一步步构造的过程来实现的。下面举个例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Grandpa { 2 protected String status = "Grandpa"; 3 protected int age = 70; 4 void func() { 5 System.out.println("Grandpa.func"); 6 } 7 void who() { 8 System.out.println(status); 9 } 10 void jump() { 11 this.func(); 12 } 13 } 14 15 class Father extends Grandpa { 16 Father() { 17 status += " --> Father"; 18 } 19 void func() { 20 System.out.println("Father.func"); 21 } 22 void who() { 23 super.who(); 24 } 25 void jump() { 26 super.jump(); 27 } 28 } 29 30 class Son extends Father { 31 Son() { 32 status += " --> Son"; 33 } 34 void func() { 35 System.out.println("Son.func"); 36 } 37 void who() { 38 super.who(); 39 } 40 void jump() { 41 super.jump(); 42 } 43 } 44 45 class Family { 46 public static void main(String args[]) { 47 Son son = new Son(); 48 son.who(); 49 son.func(); 50 son.jump(); 51 } 52 }
此程序的输出为:
Grandpa --> Father --> Son
Son.func
Son.func
这也就很好地说明了构造的过程是一级一级地进行构造的(毕竟程序不是单纯地输出 “Grandpa --> Son”)。
至于后面两个测试方法以及程序输出,只要弄清楚当前的对象究竟是谁、它的直接构造类是否含有程序要调用的方法,判断起来就容易多了。
既然是一级一级调用构造器来实例化对象,那么一个构造器环节出错,肯定就无法实例化一个对象出来。例如让Father类的构造器原型声明为Father(int require)。此时编译器就会优雅地告诉我们:
此时只要在Son的构造器中把“super(666);”添加为第一句即可。(这里或多或少让刚才分析程序中没什么效果的super关键字语句减缓点儿尴尬)。当然,这里的666只是随便说的一个数字,可以替换成任何int型数据。因为这只是Father构造器提出的一个要求(require),毕竟这个require在Father类中并没有被用到。
为了帮super关键字“挽回点儿面子”,说两句我认为的super关键字的重要性:
super关键字其实是构造器中的第一句,当构造器为默认构造器时(即程序中不具体声明实现构造器或父类构造器无需任何参数即可实例化对象),(隐身的)默认构造器第一句就是“super();”。
super关键字可以让我们更方便地调用父类的方法,甚至让我们更快捷地访问父类的成员属性。
以上就是这两天对“类和对象”内容的简单的“复习回忆记笔记”,以及通过老师给出的问题,让我对this和super关键字有了一个新的认识(讲真,之前(至少昨天的时候)真的弄错了)。
以后有了新想法再来更新,恭请欢迎读者访客给出建议修正。