`

真实的谎言——Upcasting的戏法

阅读更多

0.继续Allen Lee的大片激赏
        Allen Lee我是谁一文中探讨了Interface选择性透过的问题,可谓是绘声绘色,精彩纷呈。我虽言辞拙劣,只因自己还有几下C/C++的三脚猫功夫,又被曾经风靡一时的大片所动,遂延续Allen Lee的精彩,斗胆跟Allen抢抢生意。嘿,开场时间到了,帷幕拉开……
        首先出场的是一位长者——Michael:    

public class BaseClass {
    
public String name = "Michael";
    
public int age = 70;
    
public int grade = 2;
    
public void lie() {
        System.out.println(
"I'm Michael. My age is " + age);
    }

    
public void stump() {
        System.out.println(
"I'm too old. I stump so hard.")
    }

}

        
随后,是一位英俊的小伙子——Perhaps:

public class DerivedClass extends BaseClass {
    
public String name = "Perhaps";
    
public int age = 20;
    
public int id = 1011;
    
public void lie() {
        System.out.println(
"I'm Perhaps. My age is " + age);
    }

    
public void run() {
        System.out.println(
"I'm so young. I can run fast!");
    }

}
   

       
接着,不可思议的事情发生了。噢,英俊的小伙子怎么摇身一变,成了长者?!        

BaseClass clazz = new DerivedClass();


1.真实的谎言
        小伙子相貌成了老者,甚是惟妙惟肖,所以他大言不惭地说自己很老了,到处招摇撞骗:

System.out.println("My age is " + clazz.age); //打印出来的结果是70!

        
还不告诉别人自己的id,更可恶的是,他连跑步都不会了!

System.out.println("My id is " + clazz.id);  // 编译错误!
clazz.run();                                 // 编译错误!

       
但它的身手却依旧矫健,谎言终究变得无力:

clazz.lie();        // 还是打印I'm Perhaps. My age is 20  


2.Class Casting的威力
       虽然小伙子的变身并不完美,但是我们还是能够从中感受到变身的威力。BaseClass clazz = new DerivedClass()到底做了些什么呢?
       首先,new关键字为构造DerivedClass实例申请了一块足够大的内存空间;接着构造函数DerivedClass根据类定义创建了该类的实例。这个创建的过程包括:调用BaseClass的构造函数、初始化类变量和构建Virtual Function Table;随后,构造函数返回一个DerivedClass类型的指针;最后,该指针被Upcast成BaseClass类型的指针。前面几步,大部分朋友应该都很熟悉了,而变身的关键则在于最后一步:Upcasting。那么Upcasting又做了什么呢?且让我暂时卖个关子。

3. 揭穿真实的谎言  
       成龙大哥在直升机上被扔了下来,掉入森林中失掉了记忆;而我们的DerivedClass不是掉下来(Downcast),而是被Upcast捧上了天,成了BaseClass后就把自己的身份抛到九霄云外了。为了不让DerivedClass洋洋自得,还是要让它狠狠地摔一跤,恢复本来面目。于是,我就拿出Downcast的魔杖对在天上腾云驾雾的DerivedClass一指,霎时间DerivedClass一个倒栽葱跌下地来——噢,这小样终于原形毕露了! 

DerivedClass subClazz = (DerivedClass) clazz;
subClazz.lie();                   
// 打印I'm Perhaps.My age is 20
System.out.println(subclazz.age); // 打印结果为20,这下子终于说实话了!


4.让我们再深入一些
        好,戏都演完了,但这仍然只是个铺垫。以上讲到的问题其实并不复杂,就是通过基类指针访问派生类实例的时候,为什么无法调用子类中非继承方法呢?为什么调用到了派生类的继承方法的同时却只能访问属于基类的数据成员呢?要回答这个问题,还是先让我们看看类DerivedClass的实例在内存中的layout吧:
   

MemoryLayout.bmp


还记得前面所卖的关子吗?Upcasting的变身戏法并没有改变clazz所指向的内存位置,却改变了clazz所指向内存的大小。在Java当中,没有sizeof操作符,我无法得知clazz所指向内存的大小,但是通过相对应的C++代码可以验证以上推论。因此,将DerivedClass类型的指针UpCast为BaseClass类型的指针的时候,该指针所指向内存所包含的内容就只有图中红色框1包括的部分了,这很好地说明了为什么Upcast之后的clazz只能访问基类的数据成员。Upcasting除了改变指向内存的大小之外,还缩减了Virtual Table的长度[1],也就是另外红色框2包括的部分,这也正好回答了clazz无法调用run方法的原因。在这里,要注意Function Pointer的先后顺序:首先是重载的方法,接着是从基类继承过来的方法,最后才是类本身独有的方法。

5.还有一个问题
        我们都在关注BaseClass和DerivdeClass的数据成员以及DerivedClass独有的成员函数,却把体现多态的方法——lie()晾在了一边。lie()是让我们明察秋毫的依据,因为不管Upcast还是Downcast都没有改变它的立场。lie方法坚定的立场却引发了另外一个问题:我们可以从DerivedClass的实例中一个不落地找到BaseClass所有的数据成员,但是我们彻彻底底把BaseClass的lie方法都丢了。我们可以找到丢掉的lie方法吗?我们真的还需要它吗?

[1] 有关Virtual Table Pointer和Virtual Table的介绍可以参考Wikipedia中相关的部分
[2] 本文参考资料:Polymorphism in C    &   Inside the C++ Object Model     

分享到:
评论

相关推荐

    Java——多态与接口.rar

    向上转型(Upcasting)是指将子类对象赋值给父类引用,这种转换是安全的,因为子类具备父类的所有属性和方法(包括抽象方法,但必须在子类中实现)。这在多态场景下非常有用,比如在集合中存储不同类型的对象,只要...

    13.java学习第十三章——方法覆盖和多态.pdf

    - **向上转型**(Upcasting):将子类的对象赋值给父类的引用变量。这是一种自动类型转换,无需显式转换。 - 例如,假设有一个Animal类和它的子类Dog。可以将Dog对象赋值给Animal类型的引用。 - **向下转型**...

    java多态性详解——父类引用子类对象

    在Java中,我们可以通过父类的引用变量来指向子类的对象,这种现象被称为“向上转型”(Upcasting)。例如: ```java class Animal {} // 父类 class Cat extends Animal {} // 子类 public class Test { public ...

    Java语言程序设计教程(Java 7)——入门与提高篇04

    向上转型(upcasting)是指将子类对象赋值给父类类型的变量,这是安全的,因为子类是父类的特化。 - **向下转型(downcasting)**:则是将父类类型的变量转换为子类类型。在向下转型时需要小心,因为不是所有的父类...

    countdownlatch-example-sourcecode.zip

    《CountDownLatch实战解析与源码探索》 CountDownLatch是Java并发编程中一个非常重要的同步工具类,它在多线程协作场景中起到了关键的作用。在`countdownlatch-example-sourcecode.zip`这个压缩包中,我们可以看到...

    C++上行转换和下行转换

    为了处理下型转换的安全性问题,C++提供了一种静态类型检查机制——`dynamic_cast`。例如,如果我们使用`dynamic_cast*>(dog1);`,当`dog1`实际上不是一个`Dog`对象时,`dynamic_cast`会返回一个空指针,从而避免了...

    毕向东0801视频

    《毕向东0801视频》是一套针对Java初学者的教育视频,主要讲解了Java编程语言中的一个重要概念——多态。多态是面向对象编程的三大特性之一,与封装和继承并列,是理解面向对象编程的关键。在这个系列的视频中,毕...

    面向对象oop详解

    面向对象编程(OOP)是计算机编程中一种极为重要的范式,它的核心思想是将数据(属性)和行为(方法)封装成一个单独的实体——类。通过类的实例化,我们能够创建对象,并利用对象之间相互作用来解决实际问题。OOP的...

    Java4Android 14_面向对象基础(三)

    1. 封装:封装是面向对象编程的基础,它将数据和操作数据的方法捆绑在一起,形成一个独立的单元——对象。在Java中,我们通过类来定义对象,并使用访问修饰符(如private、public、protected)来控制成员的访问权限...

    个人java总结之面向对象

    **封装** 是面向对象的主要特征之一,它将数据和操作这些数据的方法捆绑到一个单元——类中,以保护数据不被非法访问。在Java中,我们通过访问修饰符(如public、private、protected)来控制类成员的可见性,实现...

    基对象引用子类实例(C# 形象说明:父母可以代表孩子做很多事情)

    在C#中,一个子类实例可以被声明为它的基类类型,这种现象被称为向上转型(Upcasting)。这意味着一个子类对象可以被当作基类对象来处理。例如: ```csharp public class BaseClass { public void CommonMethod() ...

    Thinking in java第4版练习题答案

    2. **封装**:封装是将数据和操作数据的方法绑定在一起,形成一个独立的实体——类。练习题可能会要求你设计具有私有(private)和公共(public)访问级别的类,以及如何通过getter和setter方法来访问和修改私有数据...

    第5章 面向对象(三).pdf

    面向对象的三大特性之一——多态,是编程中极为重要的概念。它允许我们设计出更加灵活、可扩展的代码,使程序能够以一种统一的方式处理不同的对象,即使这些对象具有不同的具体实现。在Java中,多态主要体现在方法的...

    《面向对象技术与方法》14、对象容器.pdf

    在《面向对象技术与方法》这门课程中,第十四讲主要讲述了面向对象编程中一个重要概念——对象容器。对象容器作为面向对象编程的一个核心部分,不仅在理论上具有重要意义,而且在实际应用中也非常广泛。本篇文章将...

    java 上塑造型和下塑造型的练习

    在Java编程语言中,"上塑造型"(Upcasting)和"下塑造型"(Downcasting)是面向对象编程中的重要概念,它们涉及到类的继承关系以及类型转换。本篇文章将详细阐述这两个概念,帮助初学者更好地理解Java中的类型转换。...

    Java 多态中的类型转换

    本篇将深入探讨Java多态中的类型转换,包括向上转型(Upcasting)和向下转型(Downcasting),以及相关的注意事项。 1. **向上转型(Upcasting)** 向上转型是指将子类的对象引用赋值给父类的引用。在Java中,这是...

    团队学生技术交流讲座.pptx

    3. **接口说明与向上转型(Upcasting)** 在类的继承体系中,向上转型是指将派生类的对象指针或引用赋值给基类的指针或引用。由于C++的赋值兼容原则,这是允许的。例如,在乐器类的继承结构中,可以将木管乐器...

    java编程第八讲----多态

    1、再谈向上类型转换(upcasting) 忘记对象的类型 2、多态机理 方法绑定(method binding) 产生正确的行为 可扩展性 陷阱:置换私有方法 3、抽象类与抽象方法 4、构造方法与多态 构造方法的...

    day10【接口、多态】.pdf

    在本篇文章中,我们将深入探讨接口的三大特征——多态,引用类型转换,以及接口在Java语言中的应用和实现。 ### 接口 接口是Java中的一种引用数据类型,可以被看做是一个完全抽象的类。接口中的所有成员方法都是...

    java(5)

    上溯造型(Upcasting)是将子类对象作为父类对象来处理,允许我们使用父类引用调用子类的方法,从而实现多态。例如,`doStuff(Shape s)`可以接收任何形状的子类对象,并调用`erase()`和`draw()`方法,这体现了多态的...

Global site tag (gtag.js) - Google Analytics