Subclasses and inheritance

We have just seen that one way to create subtypes is to implement an interface. Any class that implements the interface is a subtype of the interface type. There is another dimension to the subtype relationship in Java, which is that a class can be defined to be a subtype of another class, referred to as a subclass.

(The rest of this page is a review of the emergency contact example from the zyBook)

Emergency contacts

Crisis alert systems are all the rage these days. When an emergency manifests itself, all folks who have registered with the emergency contact database are notified via email, phone, text message, smoke signals, etc. We can use the concepts of inheritance to reduce the system's complexity and allow for future ways of contacting individuals.

First, we model the general Contact. All contacts should have a name, but the particular way in which they are contacted depends upon their preferred method of communication. For the moment, we'll just leave the body of the notify method more or less blank.

public class Contact {
   private String firstName;
   private String lastName;

   public Contact(String givenFirstName,
                  String givenLastName) {
      firstName = givenFirstName;
      lastName = givenLastName;
   }

   public String getName() {
      return firstName + " " + lastName;
   }

   public void notify(String alertMessage)
   {
     System.out.println("Do nothing");
   }
}

Defining a subclass using the extends keyword

Now, let's make an EmailContact which is designed for email notification. We don't have to start from scratch. An EmailContact is just like a Contact except the notify() method is different. We can define EmailContact to be a subclass of Contact using the extends keyword. EmailContact will inherit the existing attributes and methods of Contact, such as the first and last name and the getName() method. But we can override the notify() method to specialize it.

public class EmailContact extends Contact 
{
   private String emailAddress;

   public EmailContact(String givenFirstName,
                       String givenLastName,
                       String givenEmailAddress) 
   {
      // first, we call the superclass constructor to initialize the
      // "inherited" instance variables
      super(givenFirstName, givenLastName);
	 
      // then, initialize everything that is special for EmailContact
      emailAddress = givenEmailAddress;
   }

   // override the notify() method 
   public void notify(String alertMessage) 
   {
      // simulate sending an email to the address
      System.out.println("Esteemed " + getName() + ",");
      System.out.println(alertMessage);
   }
}
(Instead of System.out, we could be printing to a real email message using the Java Mail library, but let's keep it simple for now.)

Note that all the attributes and methods from the superclass Contact are inherited by the EmailContact class. In particular, getName can be called freely in notify. Constructors are never inherited, so we make a constructor for EmailContact which explicitly passes off most of the work to the superclass constructor. (The call to a superclass constructor must be the first thing done inside the subclass constructor.) This class does have an instance variable of its own, emailAddress, so we must initialize that in the constructor too.

Similarly, PhoneContact might be implemented like so:

public class PhoneContact extends Contact 
{
   private String phoneNumber;

   public PhoneContact(String givenFirstName,
                       String givenLastName,
                       String givenPhoneNumber) 
   {
      super(givenFirstName, givenLastName);
      phoneNumber = givenPhoneNumber;
   }

   public void notify(String alertMessage) 
   {
      // electronically dial phoneNumber ...
      System.out.println(alertMessage);
      System.out.println("To repeat this message, press 1.");
      System.out.println("To hear it in Spanish, press 1 also.");
   }
}

More Polymorphism

Putting it together

Now, let's pull this all together. We can make an EmergencyDatabase class that collects a bunch of Contacts together.

public class EmergencyDatabase
  private ArrayList<Contact> list;
  public EmergencyDatabase() {
      list = new ArrayList<Contact>();
   }

   public void addContact(Contact c) {
      list.add(c);
   }
   
   public void alertEverybody(String alertMessage) {
      for (int i = 0; i < list.size(); ++i) {
         Contact c = list.get(i);
         c.notify(alertMessage);
      }
   }
}

We can try out the whole system with a main method, as follows:

public class EmergencyTester {
   public static void main(String[] args) {
      EmergencyDatabase db = new EmergencyDatabase();
      db.addContact(new EmailContact("Bill", "Dollar", "xxx@iastate.edu"));
      db.addContact(new PhoneContact("Don", "Kees", "(515) 555-5555"));
      db.alertEverybody("A giraffe is loose near the campanile!");
   }
}

Using polymorphism

Look at the alertEverybody method in the line:
   c.notify(alertMessage);
What does this do? The answer is the same as it was for interfaces: The code that actually gets executed depends on the runtime type of the variable. When variable c refers to an EmailContact object, then the call to notify() executes the code in EmailContact. When c refers to a PhoneContact object, then the call to notify() executes that code.

One of the benefits of polymorphism is that as new ways of contacting people are developed, the EmergencyDatabase will not have to change. Though EmergencyDatabase knows only of Contact, the correct version of notify for the underlying, more specialized type, will always be called.

We saw the same thing in the Hero's use of the Weapon type. But there is is an important difference in the two examples. In the emergency notification example, we are accomplishing something more by using class inheritance: since Contact is a class, each of its subtypes EmailContact and PhoneContact is able to reuse the code in Contact.