Subclasses, Inheritance, and Polymorphism

Subclasses

Suppose we were writing a drawing program that needed to be able to draw circles, equilateral triangles, and rectangles. Each of these had some unique instance variables, that would be necessary for drawing them. For example, our Triangle class would need to have a sideLength variable, the Rectangle class would need to have width and height variables, and our Circle class would need to have a radius variable.

However, there were some instance variables that they should all have in common: perhaps a "color" instance variable, and an "isFilled" variable.

In Java, the best thing to do in this situation would be to first create a class, perhaps named "GeometricObject", that would contain the common instance variables, and then create subclasses of GeometricObject called Triangle, Rectangle, and Circle that would include the additional (unique) instance variables.

Here's another example. Note:

Thus, if we were turning these things into classes, we would probably want the Apple and Grape classes to be subclasses of a Fruit class, keeping the property of rotten with Fruit, and the properties involving colors and bunches in Apples and Grapes

We can create subclasses of a given class that will inherit all of the instance variables and methods of the given class (called the super class) using the keyword extends. An example can be seen in the classes defined below.

public class Fruit {
   private boolean rotten;   //by default, "false"
   
   public boolean isRotten() {
       return rotten;
   }

   public void setRotten(boolean hasRotted) {
       rotten = hasRotted;
   } 
}
public class Apple extends Fruit {
   private boolean wormsArePresent;   //by default, "false"

   public boolean hasWorms() {
       return this.wormsArePresent;      
   }

   public void setHasWorms(boolean worms) {
       this.wormsArePresent = worms;
   }
}
public class Grapes extends Fruit {
   private int numberInBunch;
   
   public Grapes(int num) {
       this.numberInBunch = num;
   }

   public int getNumberInBunch() {
       return this.numberInBunch;      
   }

   public void setNumberInBunch(int num) {
       this.numberInBunch = num;
   }
}
public class Main {

    public static void main(String[] args) {
        
        Grapes myGrapes = new Grapes(10);
        Apple myApple = new Apple();
        
        myApple.setHasWorms(true);
        
        if (myApple.hasWorms())
            myApple.setRotten(true);   
                //Note the call to the superclass method setRotten()
        
        System.out.println("I have " + myGrapes.getNumberInBunch() + 
                   " grapes in my bunch and my grapes are " + 
                   (myGrapes.isRotten() ? "" : "not ") + "rotten");  
                //we also call the superclass method isRotton() here
        
        System.out.println("It is " + myApple.hasWorms() +
                   " that my apple has worms -- my apple is " +
                   (myApple.isRotten() ? "" : "not ") + "rotten");   
                //...and here
    }
}

Here's the output:

I have 10 grapes in my bunch and my grapes are not rotten
It is true that my apple has worms -- my apple is rotten

In general:

The idea of inheritance is simple but powerful: When you want to create several classes that have some things in common, you can use subclasses so that you only have to write (and debug!) the common stuff once. Alternatively, and perhaps even more importantly, when you want to create a new class and there is already a class that includes some of the code that you want (think of all the classes at your disposal in the Java API!), you can derive your new class from the existing class. In doing this, you can reuse the instance variables and methods of the existing class without having to write (and debug!) them yourself.

The Object Class (i.e., The Cosmic Superclass)

The Object class, defined in the java.lang package, defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object, other classes derive from some of those classes, and so on, forming a hierarchy of classes.

At the top of the hierarchy, Object is the most general of all classes. Classes near the bottom of the hierarchy provide more specialized behavior.

All classes defined without an explicit extends clause automatically extend Object, so

public class Circle {
   ...
}

is equivalent to

public class Circle extends Object {
   ...
}

The Keyword super

super refers to the superclass of the class in which super appears.

This keyword can be used in a few different ways:

Invoking the Superclass Constructor

Interestingly, subclasses do NOT inherit the superclass constructor -- rather, the superclass constructor gets invoked explicitly or implicitly.

Java requires that the statement that invokes the superclass constructor using the keyword super appear first in the constructor.

If no superclass is explicitly invoked, the compiler puts super() as the first statement in the constructor.

As such...

public A() { 
}

is equivalent to

public A() {
    super();
}

and

public A(double d) {
    //some statement(s)
}

is equivalent to

public A(double d) {
    super();
    //some statement(s)
}

Constructor Chaining

The fact that super() must be called first in a subclass' constructor has consequences on the order of actions taken by the various constructors "in the chain". Consider the following classes:

public class Person {
  public Person() {
      System.out.println("Person constructor actions taken");
  }
}
public class Employee extends Person{
  public Employee() {
      System.out.println("Employee (a subclass of Person) constructor actions taken");
  }
}
public class Faculty extends Employee {
  public Faculty() {
        System.out.println("Faculty (a subclass of Employee) constructor actions taken");
  }
}
public class Main {
  public static void main(String[] args) {
      Faculty aProfessor = new Faculty();
  }
}

Here's the output of running this...

Person constructor actions taken
Employee (a subclass of Person) constructor actions taken
Faculty (a subclass of Employee) constructor actions taken

We can see the effect on constructor chaining when we use constructors that have arguments too (as opposed to the "no-arg" constructors of the last example). Consider this slight variation on the above:

public class Person {
  private String name;
  
  public Person() {
      System.out.println("Person constructor actions taken");
  }
  
  public Person(String newName) {
      this.name = newName;
      System.out.println("Person alternate constructor actions taken");
  }
  
  public String getName() {
       return this.name;
  }
  
  public void setName(String newName) {
       this.name = newName;
  }
}
public class Employee extends Person{
    
  private int employeeID;
  
  public Employee() {
      System.out.println("Employee (a subclass of Person) constructor actions taken");
  }

  public Employee(int employeeID, String name) {
       super(name);
       this.employeeID = employeeID;
       System.out.println("Employee alternate constructor actions taken");
  }
  
  public int getEmployeeID() {
      return employeeID;
  }

  public void setEmployeeID(int employeeID) {
      this.employeeID = employeeID;
  }
}
public class Faculty extends Employee {
    
  private String department;
  
  public Faculty() {
        System.out.println("Faculty (a subclass of Employee) constructor actions taken");
  }
  
  public Faculty(String department, int employeeID, String name) {
      super(employeeID, name);
      this.department = department;
      System.out.println("Faculty alternate constructor actions taken");
  }

  public String getDepartment() {
      return department;
  }
    
  public void setDepartment(String department) {
      this.department = department;
  }
}
public class Main {
  public static void main(String[] args) {
      Faculty newGuy = new Faculty("Math", 1019, "Oser");
      newGuy.setName("Paul Oser");
      System.out.println("Name: " + newGuy.getName());
      System.out.println("ID: " + newGuy.getEmployeeID());
      System.out.println("Department: " + newGuy.getDepartment());
  }
}

Here's the output:

Person alternate constructor actions taken
Employee alternate constructor actions taken
Faculty alternate constructor actions taken
Name: Paul Oser
ID: 1019 
Department: Math

Calling Superclass Methods

We can use the super keyword to call methods from the superclass too. Suppose we swap out the second constructor for the Faculty class above with the following:

public Faculty(String department, int employeeID, String name) {
      this.department = department;
      super.setEmployeeID(employeeID);
      super.setName(name);
      System.out.println("Faculty alternate constructor actions taken");
}

Here's the output:

Person constructor actions taken
Employee (a subclass of Person) constructor actions taken
Faculty alternate constructor actions taken 
Name: Paul Oser
ID: 1019
Department: Math

Note the difference in which constructors got invoked (two of them were "no-arg" constructors), even though the same faculty object was ultimately created. Do you see why this is so? (Remember, if the superclass constructor is not called explicitly, super() is implicitly called in its place as the first statement of the constructor...)

Overriding Methods in the Superclass

Subclasses, as we have seen, inherit methods from their superclass. However, if that is not desired, a subclass can modify the implementation of a method defined in the superclass. This is called method overriding.

One should note that an instance method can be overridden only if it is accessible. For example, a private method cannot be overridden, because it is not accessible outside its own class.


One very common method for objects to override is the toString() method. The toString() method is a method of the Object class, and thus inherited by all objects. If we defined the Person, Employee, and Faculty classes previously discussed in a package called "another.snippet", and if we replaced the Main class above for this package with the following:

public class Main {
    public static void main(String[] args) {
        Faculty newGuy = new Faculty("Math", 1019, "Oser");
        System.out.println(newGuy.toString());
    }
}

The resulting of the last System.out.println() statement would be something like:

another.snippet.Faculty@33f42b49

This is because our newGuy.toString() call is going "up the chain" to the first class that actually has a toString() method, which in this case goes all the way to the top of the chain, to the Object class -- and, to quote the Java API:

The toString method for class Object returns a string consisting of the name of the class of which the object is an instance, the at-sign character `@', and the unsigned hexadecimal representation of the hash code of the object.

As useful as this might be to someone who wanted to know the hash code of the newGuy, it doesn't really help anybody in the HR department -- we want the toString() method to return a string that "textually represents" our newGuy. The result should be a concise but informative representation that is easy for a person to read.

With this in mind, we should override the superclass toString() method by writing one of our own in the Faculty class. Perhaps something like...

public class Faculty extends Employee {
    
  private String department;
  
  public Faculty() {
        System.out.println("Faculty (a subclass of Employee) constructor actions taken");
  }
  
  public Faculty(String department, int employeeID, String name) {
      this.department = department;
      super.setEmployeeID(employeeID);
      super.setName(name);
      System.out.println("Faculty alternate constructor actions taken");
  }
  
  public String toString() {
       return super.getName() + ", " + this.department +
               " Department, EmployeeID #" + super.getEmployeeID();
  }

  public String getDepartment() {
       return department;
  }
    
  public void setDepartment(String department) {
       this.department = department;
  }
}

Then the statement

System.out.println(newGuy.toString());

would result in printing:

Oser, Math Department, EmployeeID #1019

Actually, we can do even better than this! The println() method automatically calls the toString() method on any object references given to it. Thus, the statement below would have produced exactly the same output:

System.out.println(newGuy);

Overriding vs. Overloading

Be careful to know the difference between overriding and overloading. When we override a method, we are replacing a method inherited from the superclass with one of our own design. When we overload a method, we are providing a method with the same name as one either in our class or in a superclass, except with a different method signature (i.e., one that has different types of arguments supplied to it).

As an example, here we override method p():

public class Test {
   public static void main(String[] args) {
       A a = new A();
       a.p(10);
   }
}
public class B {
   public void p(int i) {
   }
}
public class A extends B {
    //This method overrides the method in B
    public void p(int i) {
         System.out.println(i);
    }
}

while here we overload method p():

public class Test {
   public static void main(String[] args) {
       A a = new A();
       a.p(10);
   }
}
public class B {
   public void p(int i) {
   }
}
public class A extends B {
    //This method overloads the method in B
    public void p(double i) {
         System.out.println(i);
    }
}

Overriding the equals method

Another useful method of the Object class to override is the equals method. For example, we might say to circles are "equal" if they have the same radius. To this end, consider the following:

public class Circle {

    private double radius;

    public Circle (double r)  {
          this.radius = r;

    public boolean equals (Object o)  {
          Circle other = (Circle) o;
          return other.radius == this.radius;
    }
}

Now, you may be wondering why the Object parameter o? ...why not a Circle parameter? Remember -- we are overriding the equals() method of the Object class. If we used a Circle parameter, we would only be overloading the equals() method.

Since a general Object doesn't have a radius to compare with this.radius, we need to cast it first back to "other" Circle object we expect it to be. This brings up the whole idea of converting between subclass and superclass types...

Converting Between Subclass and Superclass Types

First, it is always ok to convert a subclass reference to a superclass reference. Consider the analogy of fruit:

We need to be very careful though, when we cast from a superclass to a subclass. It can be dangerous, in that if we are wrong about the type, an exception will be thrown.

The best thing to do if you are unsure of whether such a cast is safe is to test whether an object belongs to a particular type before casting using the instanceof operator. For example:

if (anObject instanceof BankAccount) {
    BankAccount anAccount = (BankAccount) anObject;   //if we got through the condition
    ...                                               //after the if statement, it was 
                                                      //ok to make the cast
}

More generally, the following syntax

object instanceof TypeName

will return true if the object is an instance of TypeName (or one of its subtypes) and false otherwise.

Polymorphism and Dynamic Binding

Despite the complicated sounding name, this is a really powerful feature of the Java language. Here's the idea in a nutshell:

So why is this such a powerful concept, you ask?

Suppose you create a bunch of classes, Triangle, Circle, and Rectangle that are all subclasses of a GeometricFigure class. The GeometricFigure class has a method drawMe() that doesn't do anything. Each of the subclasses overrides the drawMe() method with their own version. A Triangle object's drawMe() method draws the triangle in question; a Circle object's drawMe() method draws the circle in question, and so on...

Now suppose you create a bunch of these objects and fill a GeometricFigure[] array with their references. This is legal to do, as every reference to a Triangle, Circle, or Rectangle can always be converted to their superclass reference -- a reference to a GeometricFigure object. Now suppose, in a single for loop, traverse this array and for each geometricFigureReference in the array, you call the geometricFigureReference() method. Because of the two properties above, the Java Virtual Machine (JVM) looks at the actual object referenced, and consequently knows all of the classes and subclasses to which the referenced object belongs, and starts searching these classes for a drawMe() method. The important thing is where the JVM starts looking -- the deepest subclass. The first drawMe() methods it each time will then be either a drawMe() method of a Triangle, Circle, or Rectangle class. This first-found method is of course the one executed for each geometricFigure reference.

Thus, you can get a whole bunch of triangles, rectangles, and circles drawn with code that looks no more complicated than this:

for (int i=0; i < myGeometricFiguresRefs.length - 1; i++) {
    (myGeometricFiguresRefs[i]).drawMe();
}

That's a powerful concept!

Accessibility

We've already talked a bit about the difference between instance variables and methods with public vs. private modifiers in terms of their visibility/accessibility to other classes. There are actually 4 levels of accessibility we can declare for instance variables and methods. The two levels we haven't discussed are: the default level of accessibility (what happens if you leave off the accessibility modifier), and the level of accessibility afforded by the "protected" keyword.

The following table sums up the differences between these four:

Modifier on members in a class Accessible from the same class Accessible from the same package Accessible from a subclass Accessible from a different package
public Yes Yes Yes Yes
protected Yes Yes Yes No
default
(no modifier)
Yes Yes No No
private Yes No No No

It's important to know that a subclass are NOT allowed to weaken the accessibility of methods in its superclass.

So for example, a subclass may override a protected method in its superclass and change its visibility to public, but it could not do the same and change its visibility to private.

Similarly, if a method is defined as public in the superclass, it must be defined as public in the subclass.

The final Modifier

Similar to the keyword super, the final modifier's effect depends upon its context:

When used as a modifier on a class, it keeps the class from every being extended:

final class Math {  //you are not allowed to make a subclass of this class!
   ...
}

When used as a modifier on a variable, the variable represents a constant:

final static double PI = 3.14159

Finally (pardon the pun), when used as a modifier on a method, the method is not allowed to be overridden by any of its subclasses.