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 anEmergencyDatabase
class that collects a bunch of Contact
s 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 thealertEverybody
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
.