Upcasting

Bruce Eckel’s Thinking in Java Contents | Prev | Next

In
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.

//: 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
  }
} ///:~ 

The
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?

This
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);
  }
} ///:~ 

This
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.

More by Author

Must Read