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
Weapon
s:
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 HowitzerAnd 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 codew.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 is the type you gave the variable when you declared it
- the run-time type is the actual type of the object that the variable refers to when 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!