3.2 Inheritance: The ScreenCharacter Class and Its Subclasses

Here you will learn the basics of how to organize classes in a hierarchy. The example that we use for this is the extension of the Letter class of the previous chapter so that it works for any screen character, which can be either a letter or a digit. The applet that we are going to build up in this section looks as follows:

When you press the button, a new randomly-chosen character (letter or digit) will appear on the screen with randomly chosen position and font size.

  1. Defining Classes and Subclasses
  2. this and super
  3. Putting Things Together
    1. The Case-Sensitive Letter Applet
      1. The Applet
      2. ScreenCharacter.java
      3. Letter.java
      4. CaseSensitiveLetterApplet.java
    2. The ScreenCharacter Applet
      1. The Applet
      2. Digit.java
      3. CharacterApplet.java

Defining Classes and Subclasses

Classes form a hierarchy. For example, both letters and digits are characters that can be drawn on the computer screen. So, you can define

class ScreenCharacter {...}
and the subclasses
class Letter extends ScreenCharacter {...}
class Digits extends ScreenCharacter {...}
The keyword extends is used to distinguish the subclass (child class) and the superclass (parent class). If nothing is said about the superclass (like we have done in the definition of the ScreenCharacter class), then Java automatically considers it as a subclass of the Object class. So, with the above declarations you have constructed the following class hierarchy.

The point is that all instance variables and methods that belong to the ScreenCharacter class are inherited by its subclasses so that they need not be defined again. For example, if at the higher level the ScreenCharacter class look like

class ScreenCharacter {
  String name;
  String fontname;
  int fontsize;   
  int x, y;   

  void draw(Graphics g) {
    g.setFont(new Font(fontname, Font.BOLD, fontsize));
    g.drawString(name, x, y);
  }
}
then the subclasses Letter and Digit do not have to introduce these instance variables and method again. By the way, if you are wondering why we chose the long name ScreenCharacter instead of Character, the reason is that the Java system already has a Character class built in (in the java.lang package) and we want to avoid name clashes.

The Letter class can be introduced as

class Letter extends ScreenCharacter {
  String letterCase;

  void setCase(String letterCase) {
    this.letterCase = letterCase;
  }

  String getCase() {
    return letterCase;
  }

  void toLowerCase() {
    letterCase = "lowercase";
    name = name.toLowerCase();
  }

  void toUpperCase() {
    letterCase = "uppercase";
    name = name.toUpperCase();
  }
}
What distinguishes a letter from a character is that it is case sensitive: upper- and lowercase letters are possible. For digits case-sensitivity is not present. Above we have implemented the toUpperCase and toLowerCase methods in terms of methods with the same name in the String class. This will have the effect that we wish if it comes to drawing the letter on the screen.

this and super

What does the keyword this in the setCase method mean? The answer is that the variable this refers to "this object", i.e., the current Letter object that is receiving the call of setCase. So, this.letterCase is the instance variable of the Letter object and this instance variable differs from the argument that is also called letterCase and that is used in the definition of the setCase method. So,

in a method, the keyword this refers to the instance of the class for which the method was invoked.

In a similar way, the keyword this can be used in the very first statement in a constructor; then it will refer to another constructor of the class. In the above Letter class we could have defined the following two constructors.
Letter() {
  this("a", 12, 0, 0, "lowercase");
}

Letter(String name, int fontsize, int x, int y, String letterCase) {
  super(name, fontsize, x, y);
  this.letterCase = letterCase;
}
In the first constructor we call a constructor of the same class with explicit arguments. This second definition of a Letter constructor is called. In the definition of the second constructor we use a similar language element: the keyword super is used to refer to a constructor of the immediate superclass of the currect class. In our case, a constructor of the ScreenCharacter class is invoked. As we see,

in a method, the keyword super refers to the object for which the method was invoked as an instance of the superclass of the class in which the method appears.

Putting Things Together

1. The Case-Sensitive Letter Applet

First, we make an implementation of the ScreenCharacter class and the Letter class so that the letter example of the previous chapter can be extended to the drawing of a randomly chosen upper- or lowercase letter. The code of the applet and the Java code of the relevant files are listed below.
The Applet
ScreenCharacter.java
import java.awt.*;

class ScreenCharacter {
  String name;
  String fontname = "Helvetica";
  int fontsize;
  int x,y;

  void translate(int a, int b) {
    x = x+a;
    y = y+b;
  }
 
  int randomNumber(int low, int high) {
    return ((int) (low + Math.random()*(high-low)));
  }

  void randomize() {
    x = randomNumber(20,100);
    y = randomNumber(40,120);
    fontsize = randomNumber(8,36);
  }

  void draw(Graphics g) {
    g.setFont(new Font(fontname, Font.BOLD, fontsize));
    g.drawString(name, x, y);
  }
}
Letter.java
class Letter extends ScreenCharacter {
  String charCase;

  Letter() {
    this("a", 12, 0, 0, "lowercase");
  }

  Letter (String name, int fontsize, int x, int y, String charcase) {
    this.name = name;
    this.fontsize = fontsize;
    this.x = x;
    this.y = y;
    this.charcase = charcase;
  }

  void setCase(String charCase) {
    this.charCase = charCase;
  }

  String getCase() {
    return letterCase;
  }

  void toLowerCase() {
    letterCase = "lowercase";
    name = name.toLowerCase();
  }

  void toUpperCase() {
    letterCase = "uppercase";
    name = name.toUpperCase();
  }

  void randomize() {
    // random position and fontsize
    super.randomize();
    // random name
    int i = randomNumber(0,25);
    name = "abcdefghijklmnopqrstuvwxyz".substring(i,i+1);
    // random case
    if (randomNumber(0,2) == 0) {
      toLowercase();
    }
    else {
      toUppercase();
    }
  }
}
CaseSensitiveLetterApplet.java
import java.applet.Applet;
import java.awt.*;

public class CaseSensitiveLetterApplet extends Applet {

  Letter c;

  public void init() {
    add(new Button("randomize"));
    c = new Letter(); // create default letter a
    c.randomize(); // choose random properties of letter
  }

  public boolean action(Event e, Object arg) {
    c.randomize(); // choose random properties of letter
    repaint(); 
    return true;
  }

  public void paint(Graphics g) {
    c.draw(g);
  }
}  

2. The ScreenCharacter Applet

T obtain the final applet, we implement the Digit class and redefine the CharacterApplet class. The applet looks then as follows.
The Applet
Digit.java
class Digit extends ScreenCharacter {

  Digit() {
    this("1", 12, 0, 0);
  }

  Digit(String name, int fontsize, int x, int y) {
    this.name = name;
    this.fontsize = fontsize;
    this.x = x;
    this.y = y;
  }

  int intValue(Digit d) {
    return(Integer.parseInt(d.name));
  }

  void randomize() {
    // random position and fontsize
    super.randomize();
    // random value
    int i = randomNumber(0,9);
    name = "0123456789".substring(i,i+1);
  }
}
CharacterApplet.java
This is basically the same code as in the above file CaseSensitiveLetterApplet. The only difference lies in the random creation of a letter or a digit. The new lines of Java code are highlighted below.
import java.applet.Applet;
import java.awt.*;

public class CharacterApplet extends Applet {

  ScreenCharacter c;

  public void init() {
    add(new Button("new character"));
    c = generateCharacter(); // create randomly a letter or a digit
    c.randomize(); // choose random properties of letter or digit
  }

  public boolean action(Event e, Object arg) {
    c = generateCharacter(); // recreate randomly a letter or a digit
    c.randomize(); // choose random properties of letter or digit
    repaint(); 
    return true;
  }

  public void paint(Graphics g) {
    c.draw(g);
  }

  ScreenCharacter generateCharacter() {
    if ( 0 == (int) Math.round(Math.random())) {
      return (new Letter());
    }
    else {
      return (new Digit());
    }
  }
}