📄 tij309.htm
字号:
Stringed violin = <font color=#0000ff>new</font> Stringed();
Brass frenchHorn = <font color=#0000ff>new</font> Brass();
tune(flute); <font color=#009900>// No upcasting</font>
tune(violin);
tune(frenchHorn);
monitor.expect(<font color=#0000ff>new</font> String[] {
<font color=#004488>"Wind.play() Middle C"</font>,
<font color=#004488>"Stringed.play() Middle C"</font>,
<font color=#004488>"Brass.play() Middle C"</font>
});
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE><p><br></p>
<p>This works, but there’s a major drawback: you must write type-specific methods for each new <b>Instrument</b> class you add. This means more programming in the first place, but it also means that if you want to add a new method like <b>tune( )</b> or a new type of <b>Instrument</b>, you’ve got a lot of work to do. Add the fact that the compiler won’t give you any error messages if you forget to overload one of your methods and the whole process of working with types becomes unmanageable. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1024" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>Wouldn’t it be much nicer if you could just write a single method that takes the base class as its argument, and not any of the specific derived classes? That is, wouldn’t it be nice if you could forget that there are derived classes, and write your code to talk only to the base class? <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1025" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p><a name="Index616"></a><a name="Index617"></a><a name="Index618"></a><a name="Index619"></a>That’s exactly what polymorphism allows you to do. However, most programmers who come from a procedural programming background have a bit of trouble with the way polymorphism works. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1026" title="Send BackTalk Comment">Feedback</a></font><br></p>
<h2>
<a name="_Toc305593264"></a><a name="_Toc305628736"></a><a name="_Toc312374041"></a><a name="_Toc375545329"></a><a name="_Toc24775658"></a><a name="Heading6111"></a>The
twist</h2>
<p>The difficulty with <b>Music</b>.<b>java</b> can be seen by running the program. The output is <b>Wind.play( )</b>. This is clearly the desired output, but it doesn’t seem to make sense that it would work that way. Look at the <b>tune( )</b> method:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE> <font color=#0000ff>public</font> <font color=#0000ff>static</font> <font color=#0000ff>void</font> tune(Instrument i) {
<font color=#009900>// ...</font>
i.play(Note.MIDDLE_C);
}</PRE></FONT></BLOCKQUOTE><p><br></p>
<p>It receives an <b>Instrument</b> reference. So how can the compiler possibly know that this <b>Instrument</b> reference points to a <b>Wind</b> in this case and not a <b>Brass </b>or <b>Stringed</b>? The compiler can’t. To get a deeper understanding of the issue, it’s helpful to examine the subject of <i>binding</i>. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1027" title="Send BackTalk Comment">Feedback</a></font><br></p>
<h3>
<a name="_Toc312374042"></a><a name="_Toc375545330"></a><a name="_Toc24775659"></a><a name="Heading6119"></a>Method-call
binding<br></h3>
<p><a name="Index620"></a><a name="Index621"></a>Connecting a method call to a method body is called <i>binding</i>. When binding is performed before the program is run (by the compiler and linker, if there is one), it’s called <i>early binding</i><a name="Index622"></a>. You might not have heard the term before because it has never been an option with procedural languages. C compilers have only one kind of method call, and that’s early binding. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1028" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The confusing part of the preceding program revolves around early binding, because the compiler cannot know the correct method to call when it has only an <b>Instrument</b> reference. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1029" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The solution is called <i>late binding</i>, which means that the binding occurs at run time, based on the type of object. Late binding is also called <a name="Index623"></a><a name="Index624"></a><i>dynamic binding</i><a name="Index625"></a><a name="Index626"></a> or <i>run-time binding</i><a name="Index627"></a><a name="Index628"></a>. When a language implements late binding, there must be some mechanism to determine the type of the object at run time and to call the appropriate method. That is, the compiler still doesn’t know the object type, but the method-call mechanism finds out and calls the correct method body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1030" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>All method binding in Java uses late binding unless the method is <b>static</b> or <a name="Index629"></a><a name="Index630"></a><a name="Index631"></a><a name="Index632"></a><b>final </b>(<b>private</b> methods are implicitly <b>final</b>). This means that ordinarily you don’t need to make any decisions about whether late binding will occur—it happens automatically. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1031" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>Why would you declare a method <b>final</b>? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more important, it effectively “turns off” dynamic binding, or rather it tells the compiler that dynamic binding isn’t necessary. This allows the compiler to generate slightly more efficient code for <b>final</b> method calls. However, in most cases it won’t make any overall performance difference in your program, so it’s best to only use <b>final</b> as a design decision, and not as an attempt to improve performance. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1032" title="Send BackTalk Comment">Feedback</a></font><br></p>
<h3>
<a name="_Toc375545331"></a><a name="_Toc24775660"></a><a name="Heading6125"></a>Producing
the right behavior</h3>
<p>Once you know that all method binding in Java happens polymorphically via late binding, you can write your code to talk to the base class and know that all the derived-class cases will work correctly using the same code. Or to put it another way, you “send a message to an object and let the object figure out the right thing to do.” <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1033" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The classic example in OOP is the “shape” example. This is commonly used because it is easy to visualize, but unfortunately it can confuse novice programmers into thinking that OOP is just for graphics programming, which is of course not the case. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1034" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p><a name="Index633"></a>The shape example has a base class called <b>Shape </b>and various derived types: <b>Circle</b>, <b>Square</b>, <b>Triangle</b>, etc. The reason the example works so well is that it’s easy to say “a circle is a type of shape” and be understood.<b> </b>The inheritance diagram shows the relationships: <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0115" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p align="center"><img src="TIJ314.png" alt="TIJ314.png" border="0" ><br></p>
<p>The upcast could occur in a statement as simple as:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>Shape s = <font color=#0000ff>new</font> Circle();</PRE></FONT></BLOCKQUOTE><p><br></p>
<p>Here, a <b>Circle</b> object is created, and the resulting reference is immediately assigned to a <b>Shape</b>, which would seem to be an error (assigning one type to another); and yet it’s fine because a <b>Circle</b> <i>is</i> a <b>Shape</b> by inheritance. So the compiler agrees with the statement and doesn’t issue an error message. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1035" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>Suppose you call one of the base-class methods (that have been overridden in the derived classes):<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>s.draw();</PRE></FONT></BLOCKQUOTE><p><br></p>
<p>Again, you might expect that <b>Shape</b>’s <b>draw( )</b> is called because this is, after all, a <b>Shape</b> reference—so how could the compiler know to do anything else? And yet the proper <b>Circle.draw( )</b> is called because of late binding (polymorphism). <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1036" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The following example puts it a slightly different way:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: c07:Shapes.java</font>
<font color=#009900>// Polymorphism in Java.</font>
<font color=#0000ff>import</font> com.bruceeckel.simpletest.*;
<font color=#0000ff>import</font> java.util.*;
<font color=#0000ff>class</font> Shape {
<font color=#0000ff>void</font> draw() {}
<font color=#0000ff>void</font> erase() {}
}
<font color=#0000ff>class</font> Circle <font color=#0000ff>extends</font> Shape {
<font color=#0000ff>void</font> draw() {
System.out.println(<font color=#004488>"Circle.draw()"</font>);
}
<font color=#0000ff>void</font> erase() {
System.out.println(<font color=#004488>"Circle.erase()"</font>);
}
}
<font color=#0000ff>class</font> Square <font color=#0000ff>extends</font> Shape {
<font color=#0000ff>void</font> draw() {
System.out.println(<font color=#004488>"Square.draw()"</font>);
}
<font color=#0000ff>void</font> erase() {
System.out.println(<font color=#004488>"Square.erase()"</font>);
}
}
<font color=#0000ff>class</font> Triangle <font color=#0000ff>extends</font> Shape {
<font color=#0000ff>void</font> draw() {
System.out.println(<font color=#004488>"Triangle.draw()"</font>);
}
<font color=#0000ff>void</font> erase() {
System.out.println(<font color=#004488>"Triangle.erase()"</font>);
}
}
<font color=#009900>// A "factory" that randomly creates shapes:</font>
<font color=#0000ff>class</font> RandomShapeGenerator {
<font color=#0000ff>private</font> Random rand = <font color=#0000ff>new</font> Random();
<font color=#0000ff>public</font> Shape next() {
<font color=#0000ff>switch</font>(rand.nextInt(3)) {
<font color=#0000ff>default</font>:
<font color=#0000ff>case</font> 0: <font color=#0000ff>return</font> <font color=#0000ff>new</font> Circle();
<font color=#0000ff>case</font> 1: <font color=#0000ff>return</font> <font color=#0000ff>new</font> Square();
<font color=#0000ff>case</font> 2: <font color=#0000ff>return</font> <font color=#0000ff>new</font> Triangle();
}
}
}
<font color=#0000ff>public</font> <font color=#0000ff>class</font> Shapes {
<font color=#0000ff>private</font> <font color=#0000ff>static</font> Test monitor = <font color=#0000ff>new</font> Test();
<font color=#0000ff>private</font> <font color=#0000ff>static</font> RandomShapeGenerator gen =
<font color=#0000ff>new</font> RandomShapeGenerator();
<font color=#0000ff>public</font> <font color=#0000ff>static</font> <font color=#0000ff>void</font> main(String[] args) {
Shape[] s = <font color=#0000ff>new</font> Shape[9];
<font color=#009900>// Fill up the array with shapes:</font>
<font color=#0000ff>for</font>(<font color=#0000ff>int</font> i = 0; i < s.length; i++)
s[i] = gen.next();
<font color=#009900>// Make polymorphic method calls:</font>
<font color=#0000ff>for</font>(<font color=#0000ff>int</font> i = 0; i < s.length; i++)
s[i].draw();
monitor.expect(<font color=#0000ff>new</font> Object[] {
<font color=#0000ff>new</font> TestExpression(<font color=#004488>"%% (Circle|Square|Triangle)"</font>
+ <font color=#004488>"\\.draw\\(\\)"</font>, s.length)
});
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE><p><br></p>
<p>The base class <b>Shape</b> establishes the common interface to anything inherited from <b>Shape</b>—that is, all shapes can be drawn and erased. The derived classes override these definitions to provide unique behavior for each specific type of shape. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1037" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p><b>RandomShapeGenerator</b> is a kind of “factory” that produces a reference to a randomly-selected <b>Shape</b> object each time you call its <b>next( )</b> method. Note that the upcasting happens in the <b>return</b> statements, each of which takes a reference to a <b>Circle</b>, <b>Square</b>, or <b>Triangle</b> and sends it out of <b>next( )</b> as the return type, <b>Shape</b>. So whenever you call <b>next( )</b>, you never get a chance to see what specific type it is, since you always get back a plain <b>Shape</b> reference. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1038" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p><b>main( )</b> contains an array of <b>Shape</b> references filled through calls to <b>RandomShapeGenerator.next( )</b>. At this point you know you have <b>Shape</b>s, but you don’t know anything more specific than that (and neither does the compiler). However, when you step through this array and call <b>draw( )</b> for each one, the correct type-specific behavior magically occurs, as you can see from the output when you run the program. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0116" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The point of choosing the shapes randomly is to drive home the understanding that the compiler can have no special knowledge that allows it to make the correct calls at compile time. All the calls to <b>draw( )</b> must be made through dynamic binding. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1039" title="Send BackTalk Comment">Feedback</a></font><br></p>
<h3>
<a name="_Toc375545332"></a><a name="_Toc24775661"></a><a name="Heading6212"></a>Extensibility</h3>
<p>Now let’s return to the musical instrument example. Because of polymorphism, you can add as many new types as you want to the system without changing the <b>tune( )</b> method. In a well-designed OOP program, most or all of your methods will follow the model of <b>tune( )</b> and communicate only with the base-class interface. Such a program is <a name="Index634"></a><a name="Index635"></a><i>extensible</i><a name="Index636"></a> because you can add new functionality by inheriting new data types from the common base class. The methods that manipulate the base-class interface will not need to be changed at all to accommodate the new classes. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]Chap07_1040" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>Consider what happens if you take the instrument example and add more methods in the base class and a number of new classes. Here’s the diagram:<br></p>
<p align="center"><img src="TIJ315.png" alt="TIJ315.png" border="0" ><br></p>
<p>All these new classes work correctly with the old, unchanged <b>tune( )</b> method. Even if <b>tune( )</b> is in a separate file and new methods are added to the interface of <b>Instrument</b>, <b>tune( )</b> will still work correctly, even without recompiling it. Here is the implementation of the diagram: <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0117" title="Send BackTalk Comment">Feedback</a></font><br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: c07:music3:Music3.java</font>
<font color=#009900>// An extensible program.</font>
<font color=#0000ff>package</font> c07.music3;
<font color=#0000ff>import</font> com.bruceeckel.simpletest.*;
<font color=#0000ff>import</font> c07.music.Note;
<font color=#0000ff>class</font> Instrument {
<font color=#0000ff>void</font> play(Note n) {
System.out.println(<font color=#004488>"Instrument.play() "</font> + n);
}
String what() { <font color=#0000ff>return</font> <font color=#004488>"Instrument"</font>; }
<font color=#0000ff>void</font> adjust() {}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -