我不知道的事—多态和对象的故事 (转)

对于Java的学习者和使用者来说,对象永远是一个逃不过的劫,虽然我一直认为:学习Java等面向对象语言的人是不愁找不到对象的,因为万物皆对象嘛(但是万物总是令人遐想,此处省略一万字...)。不论你是初学者还是资深的程序员,我相信,关于对象,你总有很多很多要说的:从对象的创建到对象的使用,再到垃圾回收机制,对象的一生总是充满着神奇。

       今天要说的是一些边角料的东西,而且有点杂。我想解决的有以下两个个问题:
       1.构造器里的this关键字
       2.编译时类型和运行时类型

       当然如题,这是我不知道的事,可能在别人看来这个有点过于简单了。首先大家看看这一段代码,试着在你的大脑里运行一下,并给出一个结果。

Java代码   收藏代码
  1. class A{  
  2.         private String str = "a";  
  3.         public A(){  
  4.             System.out.println("constructor A");  
  5.             System.out.println("this in A is : " + this.getClass());  
  6.             System.out.println("this.str : " + this.str);  
  7.             System.out.println("------------------------------------");  
  8.         }   
  9.         public void fun(){  
  10.             System.out.println("A.fun() > " + str);  
  11.         }  
  12.     }  
  13.       
  14.     class B extends A{  
  15.         private String str = "b";  
  16.         public B(){  
  17.             System.out.println("constructor B");  
  18.             System.out.println("this in B is : " + this.getClass());  
  19.             System.out.println("this.str : " + this.str);  
  20.             System.out.println("-------------------------------------");  
  21.         }   
  22.         public void fun(){  
  23.             System.out.println("B.fun() > " + str);  
  24.         }   
  25.     }  
  26.       
  27.     class C extends B{  
  28.         private String str = "c";  
  29.         public C(){  
  30.             System.out.println("constructor C");  
  31.             System.out.println("this in C is : " + this.getClass());  
  32.             System.out.println("this.str : " + this.str);  
  33.             System.out.println("------------------------------------");  
  34.         }   
  35.         public void fun(){  
  36.             System.out.println("C.fun() > " + str);  
  37.         }  
  38.     }  
  39.       
  40.     public class Tester {  
  41.         public static void main(String[] args) {  
  42.             new C();  
  43.         }  
  44.     }   



      
我不知道这段程序在你的大脑里的运行结果是怎么样的,但是在我的电脑里,打印结果是这样的:

       constructor A
       this in A is : class com.tuyage.control.C
       this.str : a
               -------------------------------------------
       constructor B
       this in B is : class com.tuyage.control.C
       this.str : b
       -------------------------------------------
       constructor C
       this in C is : class com.tuyage.control.C
       this.str : c
       -------------------------------------------
       大家发现了没有,同一个构造器里的this是不是有点乱,比如在A的构造器里,this.getClass()打印的结果是class C,但是调用this.str时,打印的结果却是a而不是C类里的c。

       我们先抛开这个问题不看,毕竟当局者迷。我们看看实例化的过程:首先调用new C()后会发生的事大家都知道了,就是从父类的构造器下溯。所以在创建一个C对象时,首先进入了A的构造器(我们先忽略掉Object吧,毕竟在清明扫墓的时候又有几人还在拜祭炎帝和黄帝),此时,诞生了一个对象,这个对象是什么呢?是C类型的对象!因为我们创建的是C对象,就好比一个新生的婴儿一样,不论怎么追溯他的起源,他也不会变成他的爷爷。这个对象这时有三个实例属性,我们有图为证:



       这是程序进入A构造器之后的信息。这里有三个str,你一看就知道,这三个str分别是A的str,B的str以及C的str。会不会有人问:既然先进入了A的构造器,又怎么知道有三个str呢?

       这时候我打算使用我灰常喜欢的修辞手法:比喻或者拟人。我们假设有一个婴儿刚呱呱坠地,他的家人打算帮他起一个洋气一点的名字,这样就算以后是程序员也能用名字吸引住异性,但是他们家族有一个传统,即要根据上溯三代祖辈的名字选择后代的名字(貌似国外有些国家有这个传统),他们找来了一个仙风道骨的老和尚,这个自称老衲的人翻开了这个家族的族谱,从这个婴儿开始往上看(假设他们的族谱更新得足够快,只剩下名字没有填了),他找到了他的爷爷那一辈,发现他爷爷叫做王六,然后就去看他老爹的,发现叫做王七,于是乎,这个幸运的小孩就拥有了一个霸气内外都露的名字——王八。

       这样,我想解释三个str也不是很难了,而且后面发生的事情也都在情理之中了(或者你可以将以上的A、B、C替换成GrandFather、Father和Son,str替换为name,a、b、c替换为王六、王七和王八)。我们从C类往上看,看到B类有一个str,此时我们不关心它的值,只知道有这个属性就好了,同样的事情也发生在A类中,然后我们再从A类开始,这时,我们发现了A中的str的值为a(如下图所示),这里的this也是C类型的:



       那么我们又回到了原来的问题:this.str为什么是a而不是null?我们似乎忘了还有继承关系。我们可不可以这样假设:此时c有这个str属性但是还不知道它的值,只能拼爹的爹,C对象继承了A的str属性,暂时的值为a。这也就可以解释为什么this.str的值是a了。就好比那个孩子现在还没有名字但是别人问起来了,此时只知道他爷爷的名字,于是就说自己叫王六咯。(这也就诞生了另一个问题:如果是继承,那么为什么会存在三个而不是一个str呢?别急,先这样理解着,等会告诉你!)

       接着就进入了B类的构造器。B的str属性会更新为b,那么此时打印出来的str就是b了。同理可以解释为什么C中打印的是c了。






       那么,我们此时在A、B、C的构造器里分别增加this.fun();这样的代码会发生什么呢(代码大家自己修改吧)?新的结果出来了:
       constructor A
       this in A is : class com.tuyage.control.C
       this.str : a
       C.fun() > null
-      ------------------------------------------
       constructor B
       this in B is : class com.tuyage.control.C
       this.str : b
       C.fun() > null
       -------------------------------------------
       constructor C
       this in C is : class com.tuyage.control.C
       this.str : c
       C.fun() > c
       -------------------------------------------
       都是C.fun()!这个说明了方法调用时是和构造器不一样的(这个大家都知道),直接调用了子类的(如果有的话),也就是说都调用了C里的方法(大家可以在C的fun里面加一个输出语句)。但是str的值就发生了变化,前两个str都是null,这也就证明了前面的假设不正确,亦即不能假设此时C对象的str就是A或B的str。当然前面两个打印null是在情理之中的,因为调用的fun()方法是在C里面的,而此时C的str(即图中的第三个str)还是null的,所以打印时肯定就是null了。

       很多人看到这里肯定有一种用砖头拍死我的冲动。绕了这么一圈居然没给出正确答案!这是因为我觉得正确答案绝不是我们想要的全部,我只是把自己的分析思路记录了下来,和大家分享一下。毕竟我们不能总是处在看到题目就要答案的那种小学生阶段了。如果你稍微平静了下来,就接着看吧!

       那么怎么理解this.str的输出呢?这里就得讲讲编译时类型(编译时是什么类型的)和运行时类型了(要了解运行时类型的请点击:http://februus.iteye.com/blog/1438672)。我们假设有这样一行代码:A a = new B();B是A的子类。那么此时就会出现一个a变量,这个a的编译时类型就是A,运行时类型就换成了B。很熟悉吧,这是多态啊!编译时类型和运行时类型不同就能体现出多态。方法具有多态,而属性是没有多态性的。系统就会选择编译时类的定义作为当前属性的定义,在这个例子里就是this在编译时表示的是A,因此str也表现出A里面所定义的:a。

       为什么会出现混乱呢?很主要的一个原因就是this这个关键字太具有迷惑性了,很容易让我们把this全部当做一个东西看待。如果一个构造器里出现了this这个关键字,那么它代表着正在初始化的Java对象。这个例子的A构造器里,你可以看做A a = new C();那么,this就代表了C类型的对象了,正如我们看到的那样。而在B的构造器里,可以看做B b = new C();this还是C类型的对象。


       那么,至此我相信你应该可以很清晰地理解最初的代码了,也能明白输出结果也在情理之中。如果你还是不明白我建议你敲敲代码,然后好好Debug一下,然后联系一下多态、构造器以及初始化等知识,我相信你定能发现其中的奥妙。

 

转自:http://februus.iteye.com/blog/1473534

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值