The twist | CodeGuru

The twist

Bruce Eckel’s Thinking in Java Contents | Prev | Next The difficulty with Music.java can be seen by running the program. The output is Wind.play( ). This is clearly the desired output, but it doesn’t seem to make sense that it would work that way. Look at the tune( ) method: public static void tune(Instrument i) { […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
5 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

The


difficulty with


Music

.

java

can be seen by running the program. The output is


Wind.play( )

.


This is clearly the desired output, but it doesn’t seem to make sense


that it would work that way. Look at the


tune( )

method:

  public static void tune(Instrument i) {
    // ...
    i.play(Note.middleC);
  }

It


receives an


Instrument

handle. So how can the compiler possibly know that this


Instrument

handle points to a


Wind

in this case and not a


Brass

or


Stringed

?


The compiler can’t. To get a deeper understanding of the issue,


it’s useful to examine the subject of


binding

.

Method
call binding

Connecting


a method call to a method body is called


binding

.


When binding is performed before the program is run (by the compiler and


linker, if there is one), it’s called


early
binding

.


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.

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


Instrument

handle.

The


solution is called


late
binding

,


which means that the binding occurs at run-time based on the type of object.


Late binding is also called


dynamic
binding

or


run-time
binding

.


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.

All


method binding in Java uses late binding unless a method has been declared

final.
This means that you ordinarily don’t need to make any decisions about
whether late binding will occur – it happens automatically.

Why


would you declare a method


final

?


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


final

method calls.


Producing
the right behavior

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

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.

The


shape example has a base class called


Shape

and


various derived types:


Circle

,


Square

,


Triangle

,


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.



The


inheritance diagram shows the relationships:

The


upcast could occur in a statement as simple as:

Shape
s = new Circle();

Here,


a


Circle

object is created and the resulting handle is immediately assigned to a


Shape

,


which would seem to be an error (assigning one type to another) and yet


it’s fine because a


Circle
is

a


Shape

by inheritance. So the compiler agrees with the statement and doesn’t


issue an error message.

When


you call one of the base class methods (that have been overridden in the


derived classes):

s.draw();

Again,


you might expect that


Shape

’s


draw( )

is called because this is, after all, a


Shape

handle, so how could the compiler know to do anything else? And yet the proper


Circle.draw( )

is called because of late binding (polymorphism).

The


following example puts it a slightly different way:

//: Shapes.java
// Polymorphism in Java
 
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("Triangle.erase()");
  }
}
 
public class Shapes {
  public static Shape randShape() {
    switch((int)(Math.random() * 3)) {
      default: // To quiet the compiler
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
  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] = randShape();
    // Make polymorphic method calls:
    for(int i = 0; i < s.length; i++)
      s[i].draw();
  }
} ///:~ 

The


base class


Shape

establishes the common interface to anything inherited from


Shape

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

The


main class


Shapes

contains a


static

method


randShape( )

that produces a handle to a randomly-selected


Shape

object each time you call it. Note that the upcasting happens in each of the


return

statements, which take a handle to a


Circle

,


Square

,


or


Triangle

and send it out of the method as the return type,


Shape

.


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


Shape

handle.

main( )

contains an array of


Shape

handles filled through calls to


randShape( )

.


At this point you know you have


Shape

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


draw( )

for each one, the correct type-specific behavior magically occurs, as you can


see from one output example:

Circle.draw()
Triangle.draw()
Circle.draw()
Circle.draw()
Circle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Square.draw()

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


draw( )

are made through dynamic binding.


Advertisement

Extensibility

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


tune( )

method. In a well-designed OOP program, most or all of your methods will follow


the model of


tune( )

and communicate only with the base-class interface

.
Such a program is
extensible

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.

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:

All


these new classes work correctly with the old, unchanged


tune( )

method. Even if


tune( )

is in a separate file and new methods are added to the interface of


Instrument

,


tune( )

works correctly without recompilation. Here is the implementation of the above


diagram:

//: Music3.java
// An extensible program
import java.util.*;
 
class Instrument3 {
  public void play() {
    System.out.println("Instrument3.play()");
  }
  public String what() {
    return "Instrument3";
  }
  public void adjust() {}
}
 
class Wind3 extends Instrument3 {
  public void play() {
    System.out.println("Wind3.play()");
  }
  public String what() { return "Wind3"; }
  public void adjust() {}
}
 
class Percussion3 extends Instrument3 {
  public void play() {
    System.out.println("Percussion3.play()");
  }
  public String what() { return "Percussion3"; }
  public void adjust() {}
}
 
class Stringed3 extends Instrument3 {
  public void play() {
    System.out.println("Stringed3.play()");
  }
  public String what() { return "Stringed3"; }
  public void adjust() {}
}
 
class Brass3 extends Wind3 {
  public void play() {
    System.out.println("Brass3.play()");
  }
  public void adjust() {
    System.out.println("Brass3.adjust()");
  }
}
 
class Woodwind3 extends Wind3 {
  public void play() {
    System.out.println("Woodwind3.play()");
  }
  public String what() { return "Woodwind3"; }
}
 
public class Music3 {
  // Doesn't care about type, so new types
  // added to the system still work right:
  static void tune(Instrument3 i) {
    // ...
    i.play();
  }
  static void tuneAll(Instrument3[] e) {
    for(int i = 0; i < e.length; i++)
      tune(e[i]);
  }
  public static void main(String[] args) {
    Instrument3[] orchestra = new Instrument3[5];
    int i = 0;
    // Upcasting during addition to the array:
    orchestra[i++] = new Wind3();
    orchestra[i++] = new Percussion3();
    orchestra[i++] = new Stringed3();
    orchestra[i++] = new Brass3();
    orchestra[i++] = new Woodwind3();
    tuneAll(orchestra);
  }
} ///:~ 

The


new methods are


what( )

,


which returns a


String

handle with a description of the class, and


adjust( )

,


which provides some way to adjust each instrument.

In


main( )

,


when you place something inside the


Instrument3

array


you automatically upcast to


Instrument3

.

You


can see that the


tune( )

method is blissfully ignorant of all the code changes that have happened around


it, and yet it works correctly. This is exactly what polymorphism is supposed


to provide. Your code changes don’t cause damage to parts of the program


that should not be affected. Put another way, polymorphism is one of the most


important techniques that allow the programmer to “separate the things


that change from the things that stay the same.”


Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.