CSCI A201/A597

Lecture Notes Twenty-Nine

Second Summer 2000


Extending Classes II: Overriding & Shadowing.
A class gives us a description, a list of features that an object of that class should have. By features we mean what kind of data members and method members it should have.

By extending a class we extend our original description, and specify additional features. An object of the extended class has all the features listed in the original class and the class that extends it.

An object of the original class has the features listed in the original class. That's what we started from, anyway.

This is the reason for which we say that if class B extends class A every object of type B is also an object of type A. Just put on a pair of blinkers.

But an object of type A is not of type B (objects of type B have additional features, listed in the description of class B). Objects of type B are more specialized.

Another way to remember this is in this way: ... if Goalkeeper extends Player we can use an object of type Goalkeeper everywhere we need to use a Player

... any Player ... but not the other way around.

This is one kind of polymorphism. In our discussion in class we have distinguished between:
  • object references
  • actual objects

Both have a type. Objects are anonymous, and we refer to them by names (or object references).

Extending classes is a very simple concept. Creating composite objects from composite descriptions is as easy as putting the two descriptions together,

... unless we use the same names for different features (class members of the same kind: data, or methods)

in the two descriptions.
  • when the name of two methods collide overriding happens.
  • when variables are involved it's called shadowing.

A few examples will help clarify these concepts.
Consider this:
class Player {
  void fun() {
    System.out.println("Having fun as a Player."); 
  } 
}
 
class Goalkeeper extends Player {
  void fun() {
    System.out.println("Having fun as a Goalkeeper."); 
  } 
} 

public class One {
  public static void main(String[] args) {
    System.out.println("Welcome to Program One.");
 
    Player meola = new Goalkeeper(); 
    meola.fun();  
 
    Goalkeeper higuita = new Goalkeeper(); 
    higuita.fun();
 
    ((Player)higuita).fun(); 
 
  } 
} 

Here's what you get when you run the program: You see then,
frilled.cs.indiana.edu%javac One.java
frilled.cs.indiana.edu%java One
Welcome to Program One.
Having fun as a Goalkeeper.
Having fun as a Goalkeeper.
Having fun as a Goalkeeper.
frilled.cs.indiana.edu%

... that it's the type of the object (and not that of its reference) that really matters. If either A or B do not define fun() then there's no overriding involved, and no dynamic method lookup is involved.

But it is instructive to cover the three alternatives to the situation described above. In total: four cases.

Case 2. Player does not define fun(). Only objects of type Goalkeeper referenced through object references of type Goalkeeper will be able to invoke fun().

Casting an object of type Goalkeeper to type Player and asking for fun() ... will get you into trouble early (as early as compile time).

Case 3. Goalkeeper does not define fun(). Objects of type Goalkeeper inherit fun() from being Players (but they don't have own fun).

Case 4: Neither Player nor Goalkeeper define fun(). In that case there's really nothing to talk about.

To summarize, looking up a method for invocation involves taking into account the class of the object and the class of its reference (casting included here). The object reference's class determine the method unless the object's class also defines a method with the same name, in which case that's the one that will be invoked.

Here's another example:
class Player {
  void fun() {
    System.out.println("Having fun as a Player.");
  }
}
 
class Defender extends Player {
 
}
 
class Fullback extends Defender {
  void fun() {
    System.out.println("Having fun as a Fullback.");
  }
}
 
public class Two {
  public static void main(String[] args) {
    System.out.println("Welcome to Program Two"); 

    Defender dooley = new Defender(); 
    dooley.fun();   // fun inherited (Player)        

    Fullback baresi = new Fullback(); 
    baresi.fun();   // Fullback has its own fun        

    Defender tresor = new Fullback(); 
    tresor.fun();   // type Fullback overrides the fun that
                    // Defender inherited from Player  

    Player rijkaard = new Fullback(); 
    rijkaard.fun(); // the fun defined in Fullback is used 
                    // over the one defined in Player, since
                    // the type of the object rijkaard points
                    // to (that class) has own fun  
  } 
}

Let's run it.
frilled.cs.indiana.edu%java Two
Welcome to Program Two
Having fun as a Player.
Having fun as a Fullback.
Having fun as a Fullback.
Having fun as a Fullback.
frilled.cs.indiana.edu%
Predictable. I think so.

Once you override a function the only way you can get to it is through a super reference. You can't get to it from outside the class (through casting).

Overriding is a stronger notion than shadowing. If you shadow a variable you can still get to it from outside by casting.

But we don't want to talk about shadowing here. We never used it this semester.

We have used overriding. We redefined paint.

Let's simulate how paint works. Indeed, let's analyze that.

Before we do that, let's make two final points. One refers to super.

super is a kind of casting. It allows us to put one pair of blinkers right away.

this is a reference to the current object. ... of the same type as the object's class.

And now the program. Can you annotate it and explain what happens?
class Frame {
  protected String  myGC = "The Graphics Context from class Frame"; 
 
  protected int width, height;
  protected boolean visible; 
 
  protected void resize(int w, int h)   { 
    setSize(w, h); 
    refresh(); 
  } 

  protected void refresh()              { 
    paint(myGC); 
  } 

  protected void setVisible(boolean tF) { 
    visible = tF; 
    paint(myGC); 
  } 

  protected void setSize(int w, int h)  { 
    width = w; 
    height = h; 
  } 
 
  public void paint(String gc) {
    System.out.println("Frame: I use\n  " + gc + " \nto draw my images."); 
  } 
}

public class Window extends Frame {

  public void paint(String gc) {
    System.out.println("Window: I use\n  " + gc + " \nto draw my images.");
  } 

  public static void main(String[] args) {

    Frame f = new Window();  // you have 
    f.setSize(100, 200);     // seen this 
    f.setVisible(true);      // many times... 

    user(f);                 // you never ever see this
                             // but you know it happens
  } 

  private static void user(Frame f) {
    f.resize(200, 400); 
    // minimal interaction by the user simulated here
  } 

}
Notice: I am impressed.

If you understand how the code above works then
  • you understand overriding of methods
  • you understand why we need to override paint when doing graphics
  • you understand how paint gives you access to a graphics context

Also note: protected is like private, but allowing inheritance of the variable or method.

Last updated: August 8, 2000 by Adrian German for A201