重温经典《Thinking in java》第四版之第八章 多态(四十五)

简介: 重温经典《Thinking in java》第四版之第八章 多态(四十五)

8.4 协变返回类型

Java SE5中添加了协变返回类型,它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型:

classGrain { 
publicStringtoString() { return"Grain"; } 
} 
classWheatextendsGrain { 
publicStringtoString() { return"Wheat"; } 
} 
classMill { 
Grainprocess() { returnnewGrain(); } 
} 
classWheatMillextendsMill { 
Wheatprocess() { returnnewWheat(); } 
} 
publicclassCovariantReturn { 
publicstaticvoidmain(String[] args) { 
Millm=newMill(); 
Graing=m.process(); 
System.out.println(g); 
m=newWheatMill(); 
g=m.process(); 
System.out.println(g); 
    } 
}

/* Output:

Grain

Wheat

*///:~

 

8.5 用继承进行设计

学习了多态以后,看起来似乎所有东西都可以被继承,因为多态是一种如此巧妙的工具。事实上,当我们使用现成的类来建立新类时,如果首先考虑使用继承技术,反倒会加重我们的设计负担,使事情变得不必要地复杂起来。

更好的方式是首先选择“组合”,尤其是不能十分确定应该使用哪一种方式时。组合不会强制我们的程序设计进入继承的层次结构中。而且,组合更加灵活,因为它可以动态选择类型;相反继承在编译时就需要知道确切类型。

importstaticnet.mindview.util.Print.*; 
classActor { 
publicvoidact() {} 
} 
classHappyActorextendsActor { 
publicvoidact() { print("HappyActor"); } 
} 
classSadActorextendsActor { 
publicvoidact() { print("SadActor"); } 
} 
classStage { 
privateActoractor=newHappyActor(); 
publicvoidchange() { actor=newSadActor(); } 
publicvoidperformPlay() { actor.act(); } 
} 
publicclassTransmogrify { 
publicstaticvoidmain(String[] args) { 
Stagestage=newStage(); 
stage.performPlay(); 
stage.change(); 
stage.performPlay(); 
    } 
}

/* Output:

HappyActor

SadActor

*///:~

一条通用的准则是:用继承表达行为间的差异,并用字段表达状态上的变化。

 

8.5.1 纯继承与扩展

采取“纯粹”的方式来创建继承层次结构似乎是最好的方式,也就是说,只有在基类中已经建立的方法才可以在导出类中覆盖,如下图所示:

image.png

这被称作是纯粹的“is-a”关系,因为一个类的接口已经确定了它应该是什么。继承可以确保所有的导出来都具有基类的接口,且绝对不会少。按照上图那么做,导出来也将具有和基类一样的接口。

也可以认为这是一种纯替代,因为导出来可以完全替代基类,而在使用它们时,完全不需要知道关于子类的任何额外信息:

image.png

也就是说,基类可以接收发送给导出了的任何消息,因为二者有着完全相同的接口。我们只需从导出类向上转型,永远不需要知道正在处理的对象的确切类型。所有这一切,都是通过多态来处理的。

按照这种方式考虑,似乎只有纯粹的is-a关系才是唯一明智的做法,而所有其他设计都只会导致混乱和注定会失败。这其实也是一个陷阱,因为只要开始考虑,就会转向,并发现扩展接口才是解决特定问题的完美方案。这可以称为“is-like-a”关系,因为导出类就像是一个基类——它有着相同的基本接口,但是它还具有由额外方法实现的其他特性。

虽然这是一种有用且明智的方法,但是它也有缺点。导出类中接口的扩展部分不能被基类访问,因此,一旦我们向上转型,就不能调用那些新方法:

image.png

在这种情况下,如果我们不进行向上转型,这样的问题也就不会出现。但是通常情况下,我们需要重新查明对象的确切类型,以便能够访问该类型所扩充的方法。

 

8.5.2 向下转型与运行时类型识别

由于向上转型会丢失具体的类型信息,所以我们就想,通过向下转型——也就是在继承层次中向下移动——应该能够获取类型信息。然而,我们知道向上转型是安全的,因为基类不会具有大于导出类的接口。因此,我们通过基类接口发送的消息保证都能被接受。但是对于向下转型,例如,我们无法知道一个“几何形状”它确实就是一个“圆”,它可以是一个三角形、正方形或者其他一些类型。

要解决这个问题,必须有某种方法来确保向下转型的正确性,使我们不至于贸然转型到一个错误的类型,进而发出该对象无法接受的消息,这样做是极不安全的。

在某些程序设计语言中,我们必须执行一个特殊的操作来获得安全的向下转型。但是在Java语言中,所有转型都会得到检查!所以即使我们只是进行一次普通的加括弧形式的类型转换,在进入运行期时仍然会对其进行检查,以便保证它的确是我们希望的那种类型。如果不是,就会返回一个ClassCastException(类转换异常)。这种在运行期间对类型进行检查的行为乘坐“运行时类型识别”(RTTI)。下面的例子说明RTTI的行为:

//: polymorphism/RTTI.java // Downcasting & Runtime type information (RTTI). // {ThrowsException} classUseful { 
publicvoidf() {} 
publicvoidg() {} 
} 
classMoreUsefulextendsUseful { 
publicvoidf() {} 
publicvoidg() {} 
publicvoidu() {} 
publicvoidv() {} 
publicvoidw() {} 
} 
publicclassRTTI { 
publicstaticvoidmain(String[] args) { 
Useful[] x= { 
newUseful(), 
newMoreUseful() 
        }; 
x[0].f(); 
x[1].g(); 
// Compile time: method not found in Useful: //! x[1].u();         ((MoreUseful)x[1]).u(); // Downcast/RTTI         ((MoreUseful)x[0]).u(); // Exception thrown     } 
}

///:~

MoreUseful接口扩展了Useful接口,但是由于它是继承而来的,所以它也可以向上转型到Useful类型。我们在main方法中对数组x进行初始化时可以看到这种情况的发生。既然数组中的两个对象都属于Useful类,所以我们可以调用f和g方法。如果我们试图调用u方法,就会返回一条编译时出错消息。

如果想访问MoreUseful对象的扩展接口,就可以尝试进行向下转型。如果所转类型是正确的类型,那么转型成功;否则就会返回一个ClassCastException异常。我们不必为这个异常编写任何特殊代码,因为它指出的是程序员在程序中任何地方都可能会犯的错误。

 

8.6 总结

多态意味着“不同的形式”。在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法。

如果不运用数据抽象和继承,就不可能理解或者不可能创建多态的例子。多态是一种不能单独来看待的特性,相反它只能作为类关系“全景”中的一部分,与其他特性协同工作。

为了在自己的程序中有效地运用多态乃至面向对象的技术,必须扩展自己的编程视野,使其不仅包括个别类的成员和消息,而且还要包括类与类之间的共同特性以及它们之间的关系。尽管这需要极大的努力,但是这样做是非常值得的,因为它可以带来很多的成效:更快的程序开发过程,更好的代码组织,更好扩展的程序以及更容易的代码维护等。

目录
相关文章
|
3月前
|
算法 Java 程序员
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
68 9
|
26天前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第10天】Java零基础教学篇,手把手实践教学!
19 4
|
1月前
|
Java 编译器 程序员
Java多态背后的秘密:动态绑定如何工作?
本文介绍了Java中多态的实现原理,通过动态绑定和虚拟方法表,使得父类引用可以调用子类的方法,增强了代码的灵活性和可维护性。文中通过具体示例详细解析了多态的工作机制。
34 4
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
1月前
|
Java
java继承和多态详解
java继承和多态详解
42 5
|
1月前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第1天】Java零基础教学篇,手把手实践教学!
25 1
|
1月前
|
安全 Java 编译器
【一步一步了解Java系列】:重磅多态
【一步一步了解Java系列】:重磅多态
20 3
|
2月前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
59 9
Java——类与对象(继承和多态)
|
2月前
|
Java
Java 多态趣解
在一个阳光明媚的午后,森林中的动物们举办了一场别开生面的音乐会。它们组成了一支乐队,每种动物都有独特的演奏方式。通过多态的魅力,狗、猫和青蛙分别展示了“汪汪”、“喵喵”和“呱呱”的叫声,赢得了观众的阵阵掌声。熊指挥着整个演出,每次调用 `perform()` 方法都能根据不同的动物对象唤起对应的 `makeSound()` 方法,展现了 Java 多态性的强大功能,让整场音乐会既有趣又充满表现力。
|
3月前
|
安全 Java 编译器