💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 7.1 向上转换 在第6章,大家已知道可将一个对象作为它自己的类型使用,或者作为它的基类型的一个对象使用。取得一个对象引用,并将其作为基类型引用使用的行为就叫作“向上转换”——因为继承树的画法是基类位于最上方。 但这样做也会遇到一个问题,如下例所示(若执行这个程序遇到麻烦,请参考第3章的3.1.2小节“赋值”): ``` //: Music.java // Inheritance & upcasting package c07; class Note { private int value; private Note(int val) { value = val; } public static final Note middleC = new Note(0), cSharp = new Note(1), cFlat = new Note(2); } // Etc. class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); } } // Wind objects are instruments // because they have the same interface: class Wind extends Instrument { // Redefine interface method: public void play(Note n) { System.out.println("Wind.play()"); } } public class Music { public static void tune(Instrument i) { // ... i.play(Note.middleC); } public static void main(String[] args) { Wind flute = new Wind(); tune(flute); // Upcasting } } ///:~ ``` 其中,方法`Music.tune()`接收一个`Instrument`引用,同时也接收从`Instrument`派生出来的所有东西。当一个`Wind`引用传递给`tune()`的时候,就会出现这种情况。此时没有转换的必要。这样做是可以接受的;`Instrument`里的接口必须存在于`Wind`中,因为`Wind`是从`Instrument`里继承得到的。从`Wind`向`Instrument`的向上转换可能“缩小”那个接口,但不可能把它变得比`Instrument`的完整接口还要小。 ## 7.1.1 为什么要向上转换 这个程序看起来也许显得有些奇怪。为什么所有人都应该有意忘记一个对象的类型呢?进行向上转换时,就可能产生这方面的疑惑。而且如果让`tune()`简单地取得一个`Wind`引用,将其作为自己的参数使用,似乎会更加简单、直观得多。但要注意:假如那样做,就需为系统内`Instrument`的每种类型写一个全新的`tune()`。假设按照前面的推论,加入`Stringed`(弦乐)和`Brass`(铜管)这两种`Instrument`(乐器): ``` //: Music2.java // Overloading instead of upcasting class Note2 { private int value; private Note2(int val) { value = val; } public static final Note2 middleC = new Note2(0), cSharp = new Note2(1), cFlat = new Note2(2); } // Etc. class Instrument2 { public void play(Note2 n) { System.out.println("Instrument2.play()"); } } class Wind2 extends Instrument2 { public void play(Note2 n) { System.out.println("Wind2.play()"); } } class Stringed2 extends Instrument2 { public void play(Note2 n) { System.out.println("Stringed2.play()"); } } class Brass2 extends Instrument2 { public void play(Note2 n) { System.out.println("Brass2.play()"); } } public class Music2 { public static void tune(Wind2 i) { i.play(Note2.middleC); } public static void tune(Stringed2 i) { i.play(Note2.middleC); } public static void tune(Brass2 i) { i.play(Note2.middleC); } public static void main(String[] args) { Wind2 flute = new Wind2(); Stringed2 violin = new Stringed2(); Brass2 frenchHorn = new Brass2(); tune(flute); // No upcasting tune(violin); tune(frenchHorn); } } ///:~ ``` 这样做当然行得通,但却存在一个极大的弊端:必须为每种新增的`Instrument2`类编写与类紧密相关的方法。这意味着第一次就要求多得多的编程量。以后,假如想添加一个象`tune()`那样的新方法或者为`Instrument`添加一个新类型,仍然需要进行大量编码工作。此外,即使忘记对自己的某个方法进行重载设置,编译器也不会提示任何错误。这样一来,类型的整个操作过程就显得极难管理,有失控的危险。 但假如只写一个方法,将基类作为参数使用,而不是使用那些特定的派生类,岂不是会简单得多?也就是说,如果我们能不顾派生类,只让自己的代码与基类打交道,那么省下的工作量将是难以估计的。 这正是“多态性”大显身手的地方。然而,大多数程序员(特别是有程序化编程背景的)对于多态性的工作原理仍然显得有些生疏。