`
风子柒
  • 浏览: 55222 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

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

    博客分类:
  • java
阅读更多


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

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

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


class A{
		private String str = "a";
		public A(){
			System.out.println("constructor A");
			System.out.println("this in A is : " + this.getClass());
			System.out.println("this.str : " + this.str);
			System.out.println("------------------------------------");
		} 
		public void fun(){
			System.out.println("A.fun() > " + str);
		}
	}
	
	class B extends A{
		private String str = "b";
		public B(){
			System.out.println("constructor B");
			System.out.println("this in B is : " + this.getClass());
			System.out.println("this.str : " + this.str);
			System.out.println("-------------------------------------");
		} 
		public void fun(){
			System.out.println("B.fun() > " + str);
		} 
	}
	
	class C extends B{
		private String str = "c";
		public C(){
			System.out.println("constructor C");
			System.out.println("this in C is : " + this.getClass());
			System.out.println("this.str : " + this.str);
			System.out.println("------------------------------------");
		} 
		public void fun(){
			System.out.println("C.fun() > " + str);
		}
	}
	
	public class Tester {
		public static void main(String[] args) {
			new C();
		}
	} 


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

       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一下,然后联系一下多态、构造器以及初始化等知识,我相信你定能发现其中的奥妙。
  • 大小: 9.6 KB
  • 大小: 8.4 KB
  • 大小: 8.5 KB
  • 大小: 9.8 KB
  • 大小: 39 KB
分享到:
评论
5 楼 cs小伙 2012-04-06  
学到了很多东西啊.
4 楼 奋斗的西瓜 2012-04-05  
很不错 
3 楼 逸情公子 2012-04-04  
以下是本人就该问题的一些理解,希望可以和博主一起讨论:http://weixiaolu.iteye.com/blog/1473621
2 楼 kuailehuahua 2012-04-04  
讲的很详细,受用!!
1 楼 郭广川 2012-04-03  
顶个

相关推荐

    JavaScript面向对象三个基本特征实例详解【封装、继承与多态】

    了解过面向对象的同学应该都知道,面向对象三个基本特征是:封装、继承、多态,但是对于这三个词具体可能不太了解。对于前端来讲接触最多的可能就是封装与继承,对于多态来说可能就不是那么了解了。 封装 在说封装之...

    设计模式:可复用面向对象软件的基础--详细书签版

    如果要知道怎样恰当定义和描述设计模式,我们应该可以从他们那儿获得启发”--steve billow, journal of object-oriented programming    “总的来讲,这本书表达了一种极有价值的东西。对软件设计领域有着独特的贡献...

    Java程序员应当知道的10个面向对象设计原则

    我经常看到不同经验水平的java程序员,他们有的不知道这些OOPS 和SOLID设计原则,有的只是不知道一个特定的设计原则会带来怎样的益处,甚至不知道在编码中如何使用这些设计原则。  (设计原则)底线是永远追求高内聚...

    设计模式 创建型模式 Abstract Factory模式(抽象工厂)

    Abstract Factory模式 ...我不相信DELL的键盘,那就用HP的话,可以在HPFactory里 生产出HP的键盘和鼠标,然后自行组装。 详细见博客 http://blog.csdn.net/xiaoting451292510/article/details/8290814

    摩托罗拉C++面试题

    不过,我不建议滥用设计模式,以为它有可能使得简单问题复杂化. 7.介绍一下你对设计模式的理解。(这个过程中有很多很细节的问题随机问的) 设计模式概念是由建筑设计师Christopher Alexander提出:"每一个模式描述...

    软件设计本质论—白话面向对象

    --《C++沉思录》《C++沉思录》说的是十几年前的事了,现在大家对面向对象的回答已经是众口一词:封装、继承和多态。大家都知道,在面向对象中,一辆汽车是一个对象,汽车这个概念是一个类。汽车有漂亮的外观,把各种...

    超级有影响力霸气的Java面试题大全文档

     forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。...

    java多态的笔试题-converter-app:一个用Java编写的简单测量转换器程序

    对于仍然不知道包在 Java 中的重要性的其他人,它用于管理项目的命名空间。 想象一下,如果没有包,开发人员最终会争论,比如说,谁应该得到MyClass这个名字。 注意:你必须在你的IDE中创建一个新项目,这样这里的...

    AIC的Java课程1-6章

     培养和建立面向对象编程的思维方式,可以运用封装、继承和多态三大基本特性编写面向对象的程序。  理解和应用Java异常,常用类,IO,集合和多线程等开发技术。  课时安排  总学时:52学时 ...

    Java的六大问题你都懂了吗

    所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧!其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入...

    Spring面试题

    在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器(在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。 在典型的 IOC 场景中,容器创建了所有对象,并设置必要的属性将它们...

    VC实现的简单绘图工具

    例如:使用CShapebase指针调用DrawShape函数,因为不知道子类是什么图形,所以不知道DrawShape怎样画这个图,但是直到DrawShape函数会把这个图画好,这就达到了要求。 3、 Windows窗口绘图基本知识。 3.1 Windows ...

    Agile Java (EN)

    看完才知道原来对于面向对象还有很多自己是不懂的,java还有很多功能特性是没见过的。更进一步理解了抽象、封装、继承、多态这些原理以及初步了解了极限编程(xp)。 敏捷(agile)编程:表示轻量级过程的术语;敏捷...

    在一小时内学会 C#(txt版本)

    和 C++ 要求用户显示创建 delete 运算符不一样,它们使用新运算符创建,且没有 delete 运算符。在 C# 中它们自动由垃圾回收系统回收。 引用类型包括: ? 类 ? 接口 ? 集合类型如数组 ? 字符串 枚举 C# 中的枚举...

    javascript实现面向对象类的功能书写技巧

    学过java,c#,vb的都知道类的概念,而类具有继承、封装、多态等功能。而javascript它不是面向对象语言,它是解释性语言。 但我们同样可以使用javascript来实现继承、多态。 javascript实现类,有多种方法。 方法一:...

    javaparser:基于函数式组合子逻辑的JAVA语言分析框架

    当然如果您对这些虚头八脑的名词不感兴趣,那么,你尽可以跳过这一章,不知道什么是“函数式”,并不会影响你对这个库的理解的。 C++这几年随着gp的普及,“函数式”这个老孔乙己逐渐又被人从角落里面拽了出来。一...

    as3 接口类的用法和好处

    但 是,这个函数只能检查到上一级的类名,若继承结构复杂,可能有的继承两至三级甚至更多,在不知道继承级别的情况下,用 getQualifiedSuperclassName想知道对象的继承关系链里是否存在textClass或者shapeClass,就...

    java 面试题 总结

    forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。 redirect...

    JAVA面试题最全集

    掌握类和对象的概念,掌握面向对象编程的本质 49.静态变量和静态方法的意义,如何引用一个类的静态变量或者静态方法? 50.JAVA语言如何进行异常处理,关键字:thorws,throw,try,catch,finally 51.Object类(或者其...

Global site tag (gtag.js) - Google Analytics