合适的方法的机制。也就是说,编译器还是不知道对象的类型,但是方法 的调用机制会找出,并且调用正确的方法。后绑定机制会随语言的不同而 不同,但是你可以设想,对象里面必定存有“它属于哪种类型”的信息。 除了 static和fina方法( private方法隐含有 final的意思),Jav 的所有的方法都采用后绑定。也就是说,通常情况下你不必考虑是不是应 该采用后绑定——它是自动的 为什么要声明 final方法?我们在上一章指出,这样可以禁止别人覆写那 个方法。不过,更重要的可能还是要“关闭”它的动态绑定,或者更确切 的说,告诉编译器这里不需要使用后绑定。这样编译器就能为 final方法 生成稍微高效一些的调用代码。然而在绝大多数情况下,这种做法并不会 对程序的总体性能产生什么影响,因此最好还是只把fina当作一种设计 手段来用,而不要去考虑用它来提高性能。 产生正确的行为 旦知道Java通过后绑定实现了多态的方法调用,你就可以只编写同基 类打交道的代码了。因为你知道所有的派生类也能正确地使用这些代码。 或者换一个说法,你“向对象发一个消息,让它自己判断该做些什么。 形状”就是讲解OOP的一个经典的例子。它看起来直观,因此被广 泛使用,但是不幸的是,这会让新手误以为OoP只是用来处理图像编程 的,这显然不对。 在这个例子中,基类被称作 Shape,它有好几个派生类: Circle, Square, Triangle,等等。这个例子之所以好,是因为我们能很自然 地说“圆形是一种形状”,而听的人也能明白。下面的继承关系图体现了 这种关系 Shape nheritance draw erase Circle quare Triangle Cird draw) draw( eterenceerase() erase( erase 第6页共29页 www.wgqqh.com/shhgs/tij.html
Chapter 7: Polymorphism 第 6 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 合适的方法的机制。也就是说,编译器还是不知道对象的类型,但是方法 的调用机制会找出,并且调用正确的方法。后绑定机制会随语言的不同而 不同,但是你可以设想,对象里面必定存有“它属于哪种类型”的信息。 除了 static 和 final 方法(private 方法隐含有 final 的意思),Java 的所有的方法都采用后绑定。也就是说,通常情况下你不必考虑是不是应 该采用后绑定——它是自动的。 为什么要声明 final 方法?我们在上一章指出,这样可以禁止别人覆写那 个方法。不过,更重要的可能还是要“关闭”它的动态绑定,或者更确切 的说,告诉编译器这里不需要使用后绑定。这样编译器就能为 final 方法 生成稍微高效一些的调用代码。然而在绝大多数情况下,这种做法并不会 对程序的总体性能产生什么影响,因此最好还是只把 final 当作一种设计 手段来用,而不要去考虑用它来提高性能。 产生正确的行为 一旦知道 Java 通过后绑定实现了多态的方法调用,你就可以只编写同基 类打交道的代码了。因为你知道所有的派生类也能正确地使用这些代码。 或者换一个说法,你“向对象发一个消息,让它自己判断该做些什么。” “形状” 就是讲解 OOP 的一个经典的例子。它看起来直观,因此被广 泛使用,但是不幸的是,这会让新手误以为 OOP 只是用来处理图像编程 的,这显然不对。 在这个例子中,基类被称作 Shape,它有好几个派生类:Circle, Square,Triangle,等等。这个例子之所以好,是因为我们能很自然 地说“圆形是一种形状”,而听的人也能明白。下面的继承关系图体现了 这种关系:
下面这句就是在“上传”: Shape s new Circle() 这里先创建了一个 Circle对象,接着马上把它的 reference赋给了 Shape。看上去这像是一个错误(一种类型怎么能赋给另一种):但是由 于 Circle是由 Shape派生出来的, Circle就是一种 Shape,因此这 种做法非常正确。所以编译器会毫不含糊地接受这条语句,什么错都不 报 假设你调用了一个基类方法(派生类已经覆写这个方法) 可能你会认为,这次应该总调用 Shape的draw()了吧,因为毕竟这 是 Shape的 reference——编译器又怎么会知道还要做其它事情呢?但 是由于实现了后绑定(多态性),实际上它会调用 Circle. draw()。 下面的例程稍微作了一些变化: 7/: c07: Shapes. java Polymorphism in Java import com. bruceeckel simpletest* class Shape void draw()[ void erase(( class Circle extends Shape i void draw( System. out. println("Circle. draw()") oid erase i System. out. println("Circle erase()") class square extends Shape void draw([ System. out. println("Square. draw()")i void erased System. out. printin("Square erase()")i class Triangle extends shape void draw()[ 第7页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 7 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 下面这句就是在“上传”: Shape s = new Circle(); 这里先创建了一个 Circle 对象,接着马上把它的 reference 赋给了 Shape。看上去这像是一个错误(一种类型怎么能赋给另一种);但是由 于 Circle 是由 Shape 派生出来的,Circle 就是一种 Shape,因此这 种做法非常正确。所以编译器会毫不含糊地接受这条语句,什么错都不 报。 假设你调用了一个基类方法(派生类已经覆写这个方法): s.draw(); 可能你会认为,这次应该总调用 Shape 的 draw( )了吧,因为毕竟这 是 Shape 的 reference——编译器又怎么会知道还要做其它事情呢?但 是由于实现了后绑定(多态性),实际上它会调用 Circle.draw( )。 下面的例程稍微作了一些变化: //: c07:Shapes.java // Polymorphism in Java. import com.bruceeckel.simpletest.*; import java.util.*; class Shape { void draw() {} void erase() {} } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); } void erase() { System.out.println("Circle.erase()"); } } class Square extends Shape { void draw() { System.out.println("Square.draw()"); } void erase() { System.out.println("Square.erase()"); } } class Triangle extends Shape { void draw() {
System. out. println(" Triangle. draw()) void erase( System. out. println("Triangleerase()") /A " factory that randomly creates shapes class RandomShapeGenerator rivate Random rand new Random ()i public Shape next() switch(rand. nextInt(3))( default urn new circle() case l: return new Square () case 2: return new Triangle ()i public class Shapes t private static Test monitor new Test( private static Random Shape Generator gen ew Random Shape Generator ( public static void main(String[] args)i Shape[] s= new Shape [9] / Fill up the array with shapes: for (int 1=0; i <slength; i++) s[i]=gen. next() / Make polymorphic method calls: for(int i =0; i<slength; i++) monitor. expect(new object[] t new TestExpression ("89 (Circle Square I Triangle) +"\\ draw\\(\)", s length 基类 Shape为继承类定义了一个共用的接口—也就是说,所有的 Shape都有draw()和 erase()这两个方法。派生类会覆写这两个方 法,以提供各自所独有的行为。 Random Shape Generator是一个“工厂”,每次调用它的next() 方法的时候,它都会随机选取一个 Shape对象,然后返回这个对象的 reference。要注意,上传就发生在 return语句,它拿到的都是 Circle, Square或是 Triangle,而它返回的都是 Shape。因此每次 调用next()的时候,你都没法知道返回值的具体类型,因为传给你的永 远是普通的 Shape reference。 man()创建了一个 Shape reference的数组,然后用 Random Shape Generator. next()把它填满。这时,你只知道它们 第8页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Chapter 7: Polymorphism 第 8 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); } } // A "factory" that randomly creates shapes: class RandomShapeGenerator { private Random rand = new Random(); public Shape next() { switch(rand.nextInt(3)) { default: case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } } public class Shapes { private static Test monitor = new Test(); private static RandomShapeGenerator gen = new RandomShapeGenerator(); public static void main(String[] args) { Shape[] s = new Shape[9]; // Fill up the array with shapes: for(int i = 0; i < s.length; i++) s[i] = gen.next(); // Make polymorphic method calls: for(int i = 0; i < s.length; i++) s[i].draw(); monitor.expect(new Object[] { new TestExpression("%% (Circle|Square|Triangle)" + "\\.draw\\(\\)", s.length) }); } } ///:~ 基类 Shape 为继承类定义了一个共用的接口——也就是说,所有的 Shape 都有 draw( )和 erase( )这两个方法。派生类会覆写这两个方 法,以提供各自所独有的行为。 RandomShapeGenerator 是一个“工厂”,每次调用它的 next( ) 方法的时候,它都会随机选取一个 Shape 对象,然后返回这个对象的 reference。要注意,上传就发生在 return 语句,它拿到的都是 Circle,Square 或是 Triangle,而它返回的都是 Shape。因此每次 调用 next( )的时候,你都没法知道返回值的具体类型,因为传给你的永 远是普通的 Shape reference。 main( )创建了一个 Shape reference 的数组,然后用 RandomShapeGenerator.next( )把它填满。这时,你只知道它们
Thinking in Java 3 Editio 都是 Shape,至于更具体的,就不得而知了(编译器也一样)。但是一旦 程序运行,当你遍历数组,逐个地调用它们的draw()方法的时候,你 就会发现,draw()的行为魔法般地变成了各个具体类型的正确行为 之所以要随机选择形状,是想做得道地一点,让你相信编译器在编译的时 候也不知道该选用哪个方法。所有的draw()都必须使用后绑定。 可扩展性 现在,我们再回到乐器的例子。由于有了多态性,你就可以根据需要,往 系统里添加任意多个新类型,而不用担心还要修改tune()方法了。在 个设计良好的OOP程序中,绝大多数方法都会和tune()一样,只跟基 类接口打交道。这种程序是可扩展的,因为你可以通过“让新的数据类型 继承通用的基类”的方法,来添加新的功能。而那些与基类接口打交道的 方法,根本不需要作修改就能适应新的类。 就拿乐器为例,想想该怎样往基类里加新的方法,并且通过继承产生一些 新的类。下面就是关系图 Instrument void play o String what void adjust Wind Percussion Stringed void play o void playo void play o String what String what string whati〕 void adjust void adjust void adjust Woodwind Brass string what void adjust 第9页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 9 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 都是 Shape,至于更具体的,就不得而知了(编译器也一样)。但是一旦 程序运行,当你遍历数组,逐个地调用它们的 draw( )方法的时候,你 就会发现, draw( )的行为魔法般地变成了各个具体类型的正确行为 了。 之所以要随机选择形状,是想做得道地一点,让你相信编译器在编译的时 候也不知道该选用哪个方法。所有的 draw( )都必须使用后绑定。 可扩展性 现在,我们再回到乐器的例子。由于有了多态性,你就可以根据需要,往 系统里添加任意多个新类型,而不用担心还要修改 tune( )方法了。在一 个设计良好的 OOP 程序中,绝大多数方法都会和 tune( )一样,只跟基 类接口打交道。这种程序是可扩展的,因为你可以通过“让新的数据类型 继承通用的基类”的方法,来添加新的功能。而那些与基类接口打交道的 方法,根本不需要作修改就能适应新的类。 就拿乐器为例,想想该怎样往基类里加新的方法,并且通过继承产生一些 新的类。下面就是关系图:
这些新的类都能同原先未作修改的tune(〕方法协同工作。即便是在 tune()保存在另一个文件,而 Instrument的接口已经加入了新方法 的情况下,它也能无须重新编译而正常工作。下面就是这个关系图的实 现 77: c07: music: Music3 java // An extensible program package c07 musics; import com. bruceeckel. simpletest* class Instrument void play(note n)( System. out. println ("Instrument. play()"+ n)i String what()f return Instrument void adjust()1 class Wind extends Instrument void play(note n)t System. out. println("Wind. play()"+ n) String what( return Wind"i j void adjust()( class Percussion extends Instrument i oid play (note n) System. out. println("Percussion. play ( String what()t return "Percussion"; void adjust()i class stringed extends Instrument void play (note n)i System. out. println("Stringed play ()"t n)i String what()i return "Stringed"; 1 void adjust()I class Brass extends Wind i void play(note n) System. out. println("Brass. play()'+ n) void adjust()f System. out. println("Brass. adjust()")i wind extends wind System. out. println("Woodwind. play()+ n)i String what( return Woodwind"i) 第10页共29页 www.wgqqh.com/shhgs/tij.html
Chapter 7: Polymorphism 第 10 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 这些新的类都能同原先未作修改的 tune( )方法协同工作。即便是在 tune( )保存在另一个文件,而 Instrument 的接口已经加入了新方法 的情况下,它也能无须重新编译而正常工作。下面就是这个关系图的实 现: //: c07:music3:Music3.java // An extensible program. package c07.music3; import com.bruceeckel.simpletest.*; import c07.music.Note; class Instrument { void play(Note n) { System.out.println("Instrument.play() " + n); } String what() { return "Instrument"; } void adjust() {} } class Wind extends Instrument { void play(Note n) { System.out.println("Wind.play() " + n); } String what() { return "Wind"; } void adjust() {} } class Percussion extends Instrument { void play(Note n) { System.out.println("Percussion.play() " + n); } String what() { return "Percussion"; } void adjust() {} } class Stringed extends Instrument { void play(Note n) { System.out.println("Stringed.play() " + n); } String what() { return "Stringed"; } void adjust() {} } class Brass extends Wind { void play(Note n) { System.out.println("Brass.play() " + n); } void adjust() { System.out.println("Brass.adjust()"); } } class Woodwind extends Wind { void play(Note n) { System.out.println("Woodwind.play() " + n); } String what() { return "Woodwind"; } }