📄 chapter07.html
字号:
you could just write a single method that takes the
<A NAME="Index558"></A><A NAME="Index559"></A>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
<A NAME="Index560"></A><A NAME="Index561"></A>derived classes, and write your
code to talk only to the base class?</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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><A NAME="_Toc305593264"></A><A NAME="_Toc305628736"></A><A NAME="_Toc312374041"></A><A NAME="_Toc375545329"></A><A NAME="_Toc408018532"></A><BR></P></DIV>
<A NAME="Heading209"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
The twist</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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:</FONT><BR></P></DIV>
<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.middleC);
}</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">It receives an <B>Instrument</B>
handle. So how can the compiler possibly know that this <B>Instrument</B> handle
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
useful to examine the subject of
<I>binding</I>.</FONT><A NAME="_Toc312374042"></A><A NAME="_Toc375545330"></A><A NAME="_Toc408018533"></A><BR></P></DIV>
<A NAME="Heading210"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Method call binding<BR><A NAME="Index562"></A><A NAME="Index563"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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<A NAME="Index564"></A></I>. 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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The confusing part of the above
program revolves around early binding because the compiler cannot know the
correct method to call when it has only an <B>Instrument</B>
handle.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The solution is called <I>late
binding<A NAME="Index565"></A><A NAME="Index566"></A></I>, which means that the
binding occurs at run-time based on the type of object. Late binding is also
called <I>dynamic binding<A NAME="Index567"></A><A NAME="Index568"></A></I> or
<I>run-time binding<A NAME="Index569"></A><A NAME="Index570"></A></I>. 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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">All method binding in Java uses
late binding unless a method has been declared
<A NAME="Index571"></A><A NAME="Index572"></A><B>final</B>. This means that you
ordinarily don’t need to make any decisions about whether late binding
will occur – it happens automatically.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Why would you declare a method
<B>final</B>? As noted in the last chapter, it prevents anyone from overriding
that method. Perhaps more importantly, it effectively “turns off”
dynamic binding, or rather it tells the compiler that dynamic binding
isn’t necessary. This allows the compiler to generate more efficient code
for <B>final</B> method
calls.</FONT><A NAME="_Toc375545331"></A><A NAME="_Toc408018534"></A><BR></P></DIV>
<A NAME="Heading211"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Producing the right behavior</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The classic example in OOP is the
“<A NAME="Index573"></A>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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="Tjava107.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The upcast could occur in a
statement as simple as:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>Shape s = <font color=#0000ff>new</font> Circle();</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Here, a <B>Circle</B> object is
created and the resulting handle 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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When you call one of the base class
methods (that have been overridden in the derived classes):</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>s.draw();</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Again, you might expect that
<B>Shape</B>’s <B>draw( )</B> is called because this is, after all, a
<B>Shape</B> handle, 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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The following example puts it a
slightly different way:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: Shapes.java</font>
<font color=#009900>// Polymorphism in Java</font>
<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=#0000ff>public</font> <font color=#0000ff>class</font> Shapes {
<font color=#0000ff>public</font> <font color=#0000ff>static</font> Shape randShape() {
<font color=#0000ff>switch</font>((<font color=#0000ff>int</font>)(Math.random() * 3)) {
<font color=#0000ff>default</font>: <font color=#009900>// To quiet the compiler</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>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] = randShape();
<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();
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The main class <B>Shapes</B>
contains a <B>static</B> method <B>randShape( )</B> that produces a handle
to a randomly-selected <B>Shape</B> object each time you call it. Note that the
upcasting happens in each of the <B>return</B> statements, which take a handle
to a <B>Circle</B>, <B>Square</B>, or <B>Triangle</B> and send it out of the
method as the return type, <B>Shape</B>. So whenever you call this method you
never get a chance to see what specific type it is, since you always get back a
plain <B>Shape</B> handle.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia"><B>main( )</B> contains an
array of <B>Shape</B> handles filled through calls to <B>randShape( )</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 one output
example:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>Circle.draw()
Triangle.draw()
Circle.draw()
Circle.draw()
Circle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Square.draw()</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Of course, since the shapes are all
chosen randomly each time, your runs will have different results. 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> are made through dynamic
binding.</FONT><A NAME="_Toc375545332"></A><A NAME="_Toc408018535"></A><BR></P></DIV>
<A NAME="Heading212"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Extensibility</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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<A NAME="Index574"></A><A NAME="Index575"></A>. Such a program is
<I>extensible<A NAME="Index576"></A></I> 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><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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:</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="Tjava108.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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> works correctly without
recompilation. Here is the implementation of the above diagram:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: Music3.java</font>
<font color=#009900>// An extensible program</font>
<font color=#0000ff>import</font> java.util.*;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -