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.
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.
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
.
@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.
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.
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
.
friendly
language
text
Message
getFriendly
getLanguage
getText
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;
}