7.3. A Tale of Two Cats¶
Let’s revisit our Cat and HouseCat friends. In java-web-dev-exercises,
open src/org/launchcode/java/demos/inheritance and examine the two classes inside.
7.3.1. Inheriting Fields and Properties¶
Notice that Cat has a private string field family, representing
the biological family of all cats.
10 | private String family = "Felidae";
|
This field is not directly accessible by HouseCat since it is private.
However, it may be read via the public getter getFamily.
42 43 44 | public String getFamily() {
return family;
}
|
There is no setter for family, however. It may only be set within Cat.
It makes sense that a subclass should not be able to change the biological family
of a cat, since this field should rarely, if ever, change.
Methods of the base class Cat may be called on instances of the
subclass HouseCat as if they were defined as part of the
HouseCat.
Try it out. In your inheritance folder, create a Main class. Within that class,
write an instance of HouseCat and call some of the methods it inherits from Cat.
1 2 3 | HouseCat garfield = new HouseCat("Garfield", 12);
garfield.eat();
System.out.println(garfield.isTired()); // prints true
|
The eat method was defined in Cat, but may be called on all
HouseCat instances as well. We say: “HouseCat inherits the
method eat() from Cat.” We know we have successfully called eat() on
garfield because the printed statement indicates the cat is now tired.
7.3.2. super¶
We mention above that a subclass inherits all non-constructor
methods from its base class. Indeed, when extending a class, we will not
be able to create new instances of our subclass HouseCat using any
constructors provided by Cat.
The base class Cat has a constructor that takes a single parameter
of type double.
12 13 14 | public Cat (double aWeight) {
weight = aWeight;
}
|
But because HouseCat does not have such a constructor, the following code
does not compile:
HouseCat mittens = new HouseCat(8.4);
Cat constructors are not inherited by HouseCat. If we want
to use a Cat constructor in this subclass, we must explicitly
provide it.
To do so, look at the constructor included in HouseCat:
7 8 9 10 | public HouseCat(String aName, double aWeight) {
super(aWeight);
name = aName;
}
|
Here, the super keyword allows the subclass to access the constructor from the
base class. This call to the base class constructor must be the first line of
the subclass constructor. In the case of HouseCat, the subclass constructor
extends the original constructor by setting an additional property, name.
If a base class constructor takes no arguments, then the no-argument constructor is implicitly called for you in the subclass. A no-argument, or no-arg constructor, is just as the name implies, a constructor that takes no arguments.
For example, we can add an additional constructor in Cat:
16 17 18 | public Cat () {
weight = 13;
}
|
Then in HouseCat, we can simply define another constructor as this:
12 13 14 | public HouseCat(String aName) {
name = aName;
}
|
Even though we don’t explicitly specify that we want to call a
constructor from Cat, the no-argument constructor will be called. Now, we can
initialize a new HouseCat with only a name property and the Cat no-argument
constructor will still be applied. Back in Main, you can confirm that the base
class constructor has been called:
1 2 | HouseCat spike = new HouseCat("Spike");
System.out.println(spike.getWeight()); // prints 13
|
As a consequence of this constructor syntax, we can easily expose any constructor from the base class by providing a subclass constructor with the same signature and a body that only calls the base class constructor.
16 17 18 | public HouseCat(double aWeight) {
super(aWeight);
}
|
Warning
This constructor is a bad one, and is included merely to introduce
syntax and usage. We would not want to have a constructor for
HouseCat that didn’t initialize an essential field such as name.
7.3.3. @Override¶
Sometimes when extending a class, we’ll want to modify behavior provided by the base class. This can be done by replacing the implementation of an inherited method by a completely new method implementation. For a given method, we can do this via method overriding.
In our example, the noise method of HouseCat overrides the
method of the same name in Cat. When we override it, we should use
the @Override annotation.
Here are the methods in question.
In Cat:
69 70 71 | public String noise() {
return "Meeeeeeooooowww!";
}
|
In HouseCat:
24 25 26 27 | @Override
public String noise() {
return "Hello, my name is " + name + "!";
}
|
If we have a HouseCat object and call its noise() method, we will be
using the method defined in HouseCat.
1 2 3 4 5 | Cat plainCat = new Cat(8.6);
HouseCat cheshireCat = new HouseCat("Cheshire", 12);
System.out.println(plainCat.noise()); // prints "Meeeeeeooooowww!"
System.out.println(cheshireCat.noise()); // prints "Hello, my name is Cheshire!"
|
Similar to other Java Annotations, the @Override annotation is
not required. However, it can prevent unintentional errors, and makes it clear
when reading your code what you intend to do.
The compiler will see the annotation and check to ensure that the signatures of the base method and the overriding method match up. If they don’t, it will flag an error. This can help prevent you from inadvertently creating a method with a different signature.
Warning
When overriding a method from a base class, the method signatures must be exactly the same. Recall that the signature of a method is the method name and access level, along with it’s return type and the type and number of input parameters.
public String noise();
When overriding a method, we may call the method from the base class
that we are overriding by using super. Modify your HouseCat.noise()
method as follows:
29 30 31 32 33 34 35 36 | @Override
public String noise() {
if (isSatisfied()) {
return "Hello, my name is " + name + "!";
} else {
return super.noise(); // prints "Meeeeeeooooowww!"
}
}
|
This calls the overridden method in the base class via
super.noise(), carrying out the original behavior if the given
conditional branch is reached.
7.3.4. Object Class¶
In a previous lesson, we introduced the Special Methods equals and
toString. All classes contain default implementations of these methods that
can be overridden.
In fact, these default methods are part of a class called Object. If
a class does not explicitly extend another class, then it implicitly
extends Object. So the default implementations of equals and
toString (along with a few other
methods)
are made available to us via inheritance.
Note that we should use the @Override annotation when we provide new
implementations of these methods as well.
7.3.6. Check Your Understanding¶
Question
For this question, refer to the code block below.
public class Message {
private boolean friendly = true;
private String language;
private String text;
public Message(String aLanguage, String aText) {
language = aLanguage;
text = aText;
}
public boolean getFriendly() {
return friendly;
}
public String getLanguage() {
return language;
}
public String getText() {
return text;
}
}
A class called Greeting extends Message. Greeting and
Message are both defined within a package called Speech.
Select all of the fields and methods that are inherited by
Greeting.
friendlylanguagetextMessagegetFriendlygetLanguagegetText
Question
For this question, refer to the code block below.
public class Message {
private boolean friendly = true;
private String language;
private String text;
public Message(String aLanguage, String aText) {
language = aLanguage;
text = aText;
}
public boolean getFriendly() {
return friendly;
}
public String getLanguage() {
return language;
}
public String getText() {
return text;
}
}
A class called Greeting extends Message. What would a constructor for
Greeting need to be to call the Message constructor?
public Greeting(String aLanguage, String aText, boolean isFriendly) { super(aLanguage, aText); friendly = isFriendly; }
public Greeting(String aLanguage, String aText) { super(aLanguage, aText); }
public Greeting() { super(aLanguage, aText); }
public Greeting(String aLanguage, String aText) { language = aLanguage; text = aText; }
