CSCI 201 Lab 13 -- Inheritance in Java

We'll study inheritance this week.

Getting ready

In this lab you'll complete two different Java projects, but only one of them comes with a starter jar file. Download the jar file, Pets.jar, and save it into the csci/201 directory.

Extract the archive file; consult Downloading a Java archive from Lab 2 if you have forgotten how to do that.

How Inheritance Works in Java

Java allows you to define a new class as a subclass of a previously defined class. This means that your new class can automatically possess, or inherit, useful features of the original class. All you have to do is state that your new class extends the existing class, which is called the superclass of your new class. If you start your class definition this way, then you only have to provide any additional data and methods which your more specialized class requires.

This concept in fact allows you to build "subclasses of subclasses" in Java, where a class derived from some superclass may itself be used a superclass to derive even more specialized classes. In fact Java is based on the notion of a so-called "tree" of classes, which is usually called the class hierarchy.

The Object class

As it happens, every class you have defined so far in this course has been a subclass, even if you have never used the keyword extends. If you leave out this clause, the Java compiler implicitly inserts the clause extends java.lang.Object for you. In other words, java.lang.Object is the "root" class in the entire Java class hierarchy.

The keyword Protected

These are the basic concepts of inheritance. But as you might expect, there are important details that have been left out so far. The first is that any data member and/or methods declared as private in a superclass cannot be directly accessed in the subclass. Up to now you have been advised to make all data private, and provide public methods that allow users to manipulate that data indirectly. Since it is often desirable for subclasses to have direct access to at least some superclass data, even if that data should not be available to just anyone, a new level of access is required here. This new type of access is called protected. Any data (or method) declared as protected in a class is accessible to methods of the class itself, and also to any of its subclasses. It also happens to be available to any classes defined within the same Java package, but not to other classes in general.

Overriding an inherited method

In some cases a method inherited from a superclass is not exactly suitable in its original form for a particular subclass. In these cases it is possible to override the inherited method by writing a new implementation in the subclass definition.

Polymorphism

Another key point is that inheritance is an "is-a" sort of relationship. By this we mean that if A is a superclass of B, an object b of class B may also be regarded as an object of class A. This is the same as saying that a cat is also a mammal. Now in Java this implies that it is always legal to make a statement like


Object obj = new String ("Hi there");

This works because a String is also an Object. Thus Object references can point to any type of object, because all classes are ultimately derived from Object.

Your Task

In this lab, you will have a chance to work with two short projects designed to illustrate the key concepts and features of inheritance. The first is a console application dealing with household pets, which is based on a simple class hierarchy that can be represented as


Object
  Pets
    Cat
      AlleyCat
    Dog

Before you start to panic, be assured that most of the classes in this hierarchy have already been written for you. Your major task will be simply to define the class AlleyCat and test its features.

The second project will be a simple windows application, which will use an existing "generic" window class named java.awt.Frame to create and display a top level window for a "toy" application. Again, this will only take a few lines of code on your part.

Instructions for Project 1

Start jGRASP and load the project Pets.gpj located in your Lab13/Pets subdirectory. Compile the four Java files, Pets.java, Pet.java, Dog.java, and Cat.java in your project and then run the main method of class Pets.

The class Pet is intended to represent a generic sort of household pet (cat, dog, hamster, whatever). In other words, Pet serves as a superclass from which we can derive various subclasses like Cat or Dog to define more specific types of pets.

Now most pets, including most cats and dogs, are given some sort of name (whether they know it or not). So it makes sense to provide the Pet class with data and methods that deal with a pet's name. Before proceeding, you should read through the Pet class definition to check out these features. While you are looking, verify that Pet does not explicitly use extends in its header line. This means that Pet is a direct subclass of Object.

Now look at the code of the Cat class. Notice the class header line: "public class Cat extends Pet". This means that Cat is defined to be a subclass of Pet, which in turn means that every Cat automatically inherits all the public and protected methods of Pet. If you read through the set of class methods for Cat, you will find no methods called either setName or getName. However, every Cat object does have both methods because it inherits them from Pet. The Cat definition does include some specialized methods that not all pets should possess, as for example the meow method.

As you read through the Cat definition, you will also notice some methods that make references to objects of the class Dog, which is also included in this project. These methods are provided here to allow some "message-passing" to go on between Cat and Dog objects. While they illustrate other important principles of object oriented programming, these message-passing features are not directly related to inheritance. Actually, in this lab they are just a cheap ploy to introduce gratuitous violence into an otherwise boring console application.

Your task

Now you need to add a new Java source file called AlleyCat.java to this project. Within this file, define a class AlleyCat that extends Cat, and implements the following additional methods:

  1. public void hiss ()
  2. public void snarl ()

You should implement these methods so that they simply print the console messages "Hiss!!!" and "Snarl!!!" respectively.

Finally, it seems appropriate to make an AlleyCat more assertive even when it just meows. This gives you an opportunity to override a method inherited from Cat, namely meow. To do this, simply locate the meow method of Cat and copy its code into AlleyCat.java. Then change its console message from "Meow!" to "MEOW!!".

This completes the definition of your new AlleyCat class. Now you need to test the behavior of an AlleyCat. To do this, open the project application file Pets.java. Locate the lines that create and "introduce" a new Cat object. Copy these lines and modify them so that they create and introduce a new AlleyCat, as well as the original Cat. Also add in lines that let the AlleyCat show that it can hiss and snarl. To restore peace and quiet to the application, you might also want to locate and "comment out" the line that starts the fight between the Cat and the Dog.

Compile and run the project, and verify that your AlleyCat behaves as required.

Lab Check-off 1

After you have completed and tested your new AlleyCat class, show the source file to your instructor. Demonstrate its behavior by running the program.

Instructions for Project 2

Create a new jGRASP project called Frames.gpj within the directory ~/csci/201/Lab13/Frames. (Note: you will have to create the Frames subdirectory first.)

In this project, create a new java source file called TestFrame.java and add it to your project. Within this file define a class TestFrame, which extends the standard Java library class java.awt.Frame. Incidentally, to save yourself some writing, it would be best to precede your new class definition with the two import statements:


import java.awt.*;
import java.awt.event.*;

Among other things, you can now simply write Frame instead of java.awt.Frame in this file.

Next go ahead and write the header line for the TestFrame class definition, and follow it with an empty pair of curly braces. In the following steps you will start to add data and methods to this class.

Your new TestFrame will inherit a lot of functionality from the basic Frame class. In fact, the major point of this exercise is to show just how little it takes to start building a specialized windows application if you just subclass "off-the-shelf" classes like Frame. But before you start to make a true subclass of Frame, you should see what the Frame class itself can do. To check this, just include the following main method in "TestFrame.java":

public static void main (String [] args)
  {
    Frame f = new Frame ("Test Frame");  // Makes toplevel window with title
    f.setSize (400, 300);                // Initial width=400, height=300 
    f.setVisible (true);                 // Makes window appear on screen
  }

In general, a class may include a main method like this to create and test objects of its own class. Later you will change this code slightly to create a TestFrame object, but as it stands this code creates an ordinary Frame instead.

Now you should attempt to compile and run this application as it stands. Once you fix any compiler errors and actually start it running, you should see the following blank window appear in the upper left corner of the screen:
Frame

You should check out the behavior of this window. You will find it has all the usual features, except for one: you cannot close it by clicking the little X in the rightmost part of the window manager bar. In fact, this is the only major failing of the Frame class. The only way you can close this Frame just now is to select and terminate the console window or by using the Kill button in jGRASP's Run I/O panel. Incidentally, this will kill both the Frame and the console window.

Adding a Button

Now you will proceed to make TestFrame a true subclass of Frame, by customizing it to provide at least one civilized way to dismiss the window. First, you need to add a new instance variable called exit of type Button.

Do this by adding a declaration for Button
  private Button exit;
Then write in the following constructor:

  public TestFrame (String title)
  {
    super (title);     // Invokes superclass (Frame) constructor used above

    exit = new Button ("Exit");    // Creates new Button with "Exit" label

    setLayout (new FlowLayout ()); // Creates object to control window layout
    add (exit);                    // Adds button component to window
  }

Finally, go back to the main and changes its first line to:
  TestFrame f = new TestFrame ("Test Frame");
Now main will be testing a true TestFrame object.

Now attempt to compile and run the project again. If all goes well, the window should now appear as shown below:
Test Frame

Adding a event handler

But unfortunately, you will soon discover that clicking the Exit button does not yet dismiss the window. It just isn't enough for it to have the label "Exit". As the final stage in this exercise, you will now introduce an event handler that can react to the user click on this button and stop the application.

This part takes two separate steps. First, you need to provide an object which is "qualified" to receive the "button-click" category of system event messages. Now it turns out that you can make the TestFrame itself qualified to deal with these messages. To do so, it only has to implement the ActionListener interface. In other words, the class header must be modified to include the clause "implements ActionListener" which implies that TestFrame must provide a method whose header has the form
  public void actionPerformed (ActionEvent e)

Now when everything is set up correctly, this method will be invoked by the runtime system whenever the Exit button is clicked. So you want to implement this method in such a way that it will close the window and terminate the application. You can accomplish this with the following actionPerformed method.

  public void actionPerformed (ActionEvent e)
  {
    setVisible (false);      // Makes window disappear from screen
    dispose ();              // Frees up any window resources
    System.exit (0);         // Stops Java Virtual Machine (JVM)
  }

Now make these additions to your TestFrame definition, compile the project, and run it again. But do not be disappointed if the Exit button still doesn't work, because there is still one small step left...

The only remaining problem is that the TestFrame is not yet being notified by the runtime system when the user clicks the Exit button. By now it is fully qualified to respond to such events, but it just is not getting the word. In the Java "event model", any object which may be the source of an event must set up a list of qualified listener objects that the system should notify when the event happens. The system will notify all objects in that list, but no others.

This means that the Exit button must add the TestFrame to a list of ActionListener objects that will be notified when the user clicks the button. To do this, just return to the TestFrame constructor. Immediately after the line that creates the new Exit button object, add the single line
  exit.addActionListener (this); // Notify TestFrame if button clicked

Then once again, compile and run the project. With luck, you will see that your fine Exit button is finally doing its job. If so, it is time to quit while you are ahead, at least for this lab...

But if you want to have a little more fun, change the label of your button to "Make me angry" and the entire body of the method actionPerformed to the single call setBackground(Color.RED).

You can listen to two buttons, but that requires you to use the getSource method of ActionEvent to determine which Button is responsible for the even.

Lab Check-off 2

After you have completed and tested your program, show it to your instructor.