Bruce Eckel’s Thinking in Java | Contents | Prev | Next |
Chapter 6 you saw how an object can be used as its own type or as an object of
its base type. Taking an object handle and treating it as the handle of the
base type is called
upcasting
because of the way inheritance trees are drawn with the base class at the top.
also saw a problem arise, which is embodied in the following: (See page
97
if you have trouble executing this program.)
//: 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 } } ///:~
method
Music.tune( )
accepts an
Instrument
handle,
but also anything derived from
Instrument.
In
main( ),
you can see this happening as a
Wind
handle is passed to
tune( ),
with no cast necessary. This is acceptable; the interface in
Instrument
must exist in
Wind,
because
Wind
is inherited from
Instrument.
Upcasting from
Wind
to
Instrument
may “narrow” that interface, but it cannot make it anything less
than the full interface to
Instrument.
Why
upcast?
program might seem strange to you. Why should anyone intentionally
forget
the type of an object? This is what happens when you upcast, and it seems like
it could be much more straightforward if
tune( )
simply takes a
Wind
handle as its argument. This brings up an essential point: If you did that,
you’d need to write a new
tune( )
for every type of
Instrument
in your system. Suppose we follow this reasoning and add
Stringed
and
Brass
instruments:
//: 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); } } ///:~
works, but there’s a major drawback: You must write type-specific methods
for each new
Instrument2
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
tune( )
or a new type of
Instrument,
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.
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?