We'll study inheritance this week.
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 directory C:\Files, on Windows, or the directory csci/201, on Linux.
To extract the project, type the following commands in the windows labs.
mkdir C:\Files\Lab12\Pets cd C:\Files\Lab12\Pets C:\j2sdk1.4.0_01\bin\jar xfv C:\Files\Pets.jar
And the following commands in the Linux labs.
mkdir -p ~/csci/201/Lab12/Pets cd ~/csci/201/Lab12/Pets jar xfv ~/csci/201/Pets.jar
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.
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.
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.
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.
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
.
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.
Start jGRASP and load the project Pets.gpj located
in your Pets directory.
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.
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:
public void hiss ()
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.
After you have completed and tested your new AlleyCat
class,
show the source file to your instructor. Demonstrate its behavior
by running the program.
Create a new jGRASP project called Frames.gpj within the directory C:\Files\Lab12\Frames, on Windows, and ~/csci/201/Lab12/Frames, on Linux.
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:
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.
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:
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.
After you have completed and tested your program, show it to your instructor.