Subtypes and Polymorphism

Subtyping through interfaces

We'll start with a brief review of interfaces.

Suppose you were writing a video game. You might start out with a Hero. A Hero has a weapon in each hand and blazes through the universe fighting bad guys. Maybe the code looks like this:

public class Hero {

  private Weapon rightWeapon;
  private Weapon leftWeapon;

  ...

  public void pickUpLeft(Weapon newWeapon) {
    leftWeapon = newWeapon;
  }

  public void pickUpRight(Weapon newWeapon) {
    rightWeapon = newWeapon;
  }

  public void fight() {
    rightWeapon.use();
  }

  public void fightDirty() {
    leftWeapon.use();
    rightWeapon.use();
  }
}
A Hero fights the bad guys by using one or both weapons. But what is a Weapon? We might need lots of different kinds: Howitzers, Wet Noodles, Killer Sheep, Ice Swords, and so on. We could define a Weapon class with a gigantic conditional statement:
public class Weapon
{
  private static final int HOWITZER = 0;
  private static final int WET_NOODLE = 1;
  private static final int KILLER_SHEEP = 2;

  // etc.
  
  private int weaponType;
  
  public void use()
  {
    if (weaponType == HOWITZER)
    {
      System.out.println("KABLOOIE");
    }
    else if (weaponType == WET_NOODLE)
    {
      System.out.println("fling swat swat");
    }
    else if (weaponType == KILLER_SHEEP)
    {
      System.out.println("Baaaaaa!");
    }
    // ... and so on
	
  }
}
But this is ugly and is problematic. The problems show up later when you dream up 5 new kinds of weapons, and you have to go back and modify the code, which you have already spent precious time testing and debugging. The solution is to use an interface. All that the Hero class needs to know about any kind of weapon is that it has a use() method. So we define Weapon as an interface that specifies that one method.
public interface Weapon 
{
   public void use();
}
Notice that in the interface, there is no method body - we only specify that the method exists and has no parameters and returns void. Now we can define some types of Weapons:
public class Howitzer implements Weapon {
  public void use() {
    System.out.println("KABLOOIE");
  }
}

public class WetNoodle implements Weapon {
  public void use() {
    System.out.println("fling swat swat");
  }
}

public class KillerSheep implements Weapon {
  public void use() {
    System.out.println("Baaaaaa!");
  }
}

Each of these classes is a subtype of Weapon. A variable of type Weapon can refer to any of its subtypes. For example, the variables leftWeapon and rightWeapon in the Hero class can refer to any object that implements the Weapon interface:

hero.pickUpLeft(new WetNoodle()); 
// hero fights left-handed with WetNoodle for a while
hero.fight();
// then decides to switch weapons
hero.pickUpLeft(new Howitzer());  
// now, the leftWeapon variable refers to a Howitzer
And 6 months later, if we want to add some new kinds of weapons, we don't have to change Hero or Weapon.

Polymorphism

This phenomenon, that a variable of some type can actually refer to an object of any of its subtypes, is called polymorphism, a word meaning "many forms". If you see only the code
w.use();
there is no way to know what the result will be, because the variable w is a kind of shape-shifter - it may have many forms, and the result of the use() method depends on exactly which type of Weapon the variable w is referring to at the moment the code is being executed. Because of polymorphism, every variable actually has two types associated with it. The declared type never changes, but the run-time type can vary as the code runs:
Weapon w;                // declared type of w is Weapon
w = new Howitzer();      // now w refers to run-time type Howitzer 
w.use();                 // prints KABLOOIE
w = new KillerSheep();   // now w refers to a KillerSheep
w.use();                 // prints Baaaaaa!