Inheritance occurs when one class inherits the data and behaviours
of another, so when it's done correctly, it can cut down on
redundant/repetitive code. Inheritance is a large topic, and
this lesson introduces you to the concepts of Inheritance and
how to start coding parent and child classes.
Inheritance occurs when one class inherits attributes and methods
from another class. Classes that inherit are called
child classes (also called
sub classes). These classes inherit from
parent classes (also
called super classes). It is often done when you have a set of common
methods or attributes in a set of two or more classes: You can make the
parent class more general, and it can contain the common features. Child
classes will be more specific and will add to or modify the features inherited
from the parent class.
This is often referred to as an "is a" relationship, because most of
the time you can say something like [child class name] is a [parent class name].
For example, "Circle is a Shape" or "SalariedEmployee is an Employee". When
we first started talking about class relationships, we said "A Rose is a Flower".
The Flower class is the parent of the child class Rose.
Another example that is commonly used in OOP textbooks is a class hierarchy
of animals. Say you have a Dog class. A Dog class could have attributes for
colour, name and working (true/false - some dogs are working dogs), and
methods such as eat(), bark(), and rollOver(). You might also have a Cat
class. A Cat class could have attributes for colour, name, and declawed
(true/false), and methods such as eat(), meow(), and scratchFurniture().
Both the Cat class and the Dog class have colour and name attributes, and
they both have an eat() method. Instead of writing these attributes (and
their accessor/mutator methods) and the eat() method each time in both
classes, it's better to put these common elements into one class. Therefore,
we might design a class called Pet that contained the attributes name and
colour, and the method eat(). We would make Pet the parent class, and the
Dog and Cat class would be child classes. The Dog and Cat classes inherit
the name and colour attributes and the eat() method. We can then add working,
bark() and rollOver() to Dog, and declawed, meow() and scratchFurniture() to
Cat.
Sometimes inheritance is also called Generalization.
Inheritance in UML
Inheritance is shown with a solid line that connects the parent class to the child class(es).
There is an arrow at the end of the line that points to the parent. Example:
Coding Inheritance
Let's code a couple of classes with an inheritance relationship. First,
examine the UML for these two classes: Circle models a circle object
with a specific radius, and Cylinder models a cylinder object
with a specific radius and height. Both Circle and Cylinder
can calculate their area (for Cylinder, that would be the
surface area). Only Cylinder can calculate its volume. Circle
can calculate its circumference.. but wouldn't it make sense that
someone might want to know the circumference of a cylinder, also?
Both classes have a a toString() and an equals() method.
You will see some redundancies between these two classes.
Not only does this mean the programmer is writing some code twice, but any
modifications need to be made to the common code in the future will result
in a lot of unnecessary maintenance. This is not desirable and goes against
the point of object-oriented programming.
Inheritance means that one class can inherit or use code from another class
as long as there is a child-parent relationship between the classes. If we
make Circle the parent class, we can then program Cylinder as the child class.
This will allow Cylinder to make use of code in the Circle class.
To see how this works, write the code for the Circle class:
public class Circle {
private double radius = 1;
public Circle() {
}
public Circle(double radius) throws IllegalArgumentException {
setRadius(radius);
}
public void setRadius(double radius) {
if (radius > 0)
this.radius = radius;
else
throw new IllegalArgumentException("Invalid value for radius.");
}
public double getRadius() {
return radius;
}
public double calcArea() {
return Math.PI * Math.pow(radius, 2);
}
public double calcCircumference() {
return 2 * Math.PI * radius;
}
public String toString() {
return "Circle: radius=" + radius;
}
public boolean equals(Object circle) {
Circle c = (Circle)circle;
return (c.getRadius() == this.radius);
}
}
Test your class to make sure everything works. Then we can start writing the
Cylinder class.
When designing Cylinder, we realize that the radius attribute and its
accessor and mutator methods are already defined in Circle. We also see
that we have three methods that are the same (getArea(), toString(),
and equals()) but will have different code. We will focus on these items
as we go through this example.
To indicate that a class is inheriting from another class, you use the
extends keyword in the class signature:
public class Cylinder extends Circle {
}
This indicates that Cylinder is a child class of Circle. You don't have to
define the radius variable or its get and set methods in Cylinder" Cylinder
inherits these members from the Circle class. We do need to
define the height variable and it's accessor and mutator methods,
because these members are not part of the Circle class, only the
Cylinder class:
public class Cylinder extends Circle {
private double height = 1;
public void setHeight(double height) {
if (height > 0)
this.height = height;
else
throw new IllegalArgumentException("Invalid value for height.");
}
public double getHeight() {
return height;
}
}
We also need the two constructors for this method. Constructors are
NOT inherited from the parent class, so if you want more than a default
constructor (recall that the compiler will add a default constructor
to a class if you don't add any constructors), you will have to add
them to your child classes. Our default constructor
should set radius and height to the default values, and two-parameter constructor
will need to validate radius and height by using their set methods.
In the default constructor, we could just assign radius a value of 1:
public class Cylinder extends Circle {
private double height = 1;
public Cylinder() {
radius = 1;
}
public void setHeight(double height) {
if (height > 0)
this.height = height;
else
throw new IllegalArgumentException("Invalid value for height.");
}
public double getHeight() {
return height;
}
}
However, radius is private inside the Circle class..
we can't access it!
We could use the setRadius() method, since that's a public
method inside the Circle class, it's inherited by Cylinder.
Instead, it would be easier to just
tell the parent class's default constructor to execute. You can call a parent
constructor from a child constructor by using the
super keyword:
public class Cylinder extends Circle {
private double height = 1;
public Cylinder() {
super();
}
public void setHeight(double height) {
if (height > 0)
this.height = height;
else
throw new IllegalArgumentException("Invalid value for height.");
}
public double getHeight() {
return height;
}
}
In this case, we are calling the parent class default constructor, which sets
the radius to a default value of 1.
In the second constructor, we want to set radius and height to the
programmer-specified values. Here we can use the set method for radius
(defined in the parent class) and the set method for height we just added
to our Cylinder class. However, what if we had other code in the
Circle's constructors that needed to execute when we instantiated a Cylinder?
We are already calling the parent constructor in the Cylinder's default constructor,
but what about Cylinder's 2-param constructor? This often happens, so when you
have any kind of constructor in a child class, you should always call the parent
constructor. In the case of the Cylinder 2-param constructor, you can call the
Circle's 1-param constructor and give it the radius specified in the Cylinder
constructor:
public class Cylinder extends Circle {
private double height = 1;
public Cylinder() {
super();
}
public Cylinder(double radius, double height) {
super(radius);
setHeight(height);
}
public void setHeight(double height) {
if (height > 0)
this.height = height;
else
throw new IllegalArgumentException("Invalid value for height.");
}
public double getHeight() {
return height;
}
}
This code will invoke the single-parameter Circle constructor, passing the
value of radius into it. Obviously this will run the
setRadius() method in the
Circle class, which validates and sets the radius variable. This is a common
way for programmers to set values that are inherited from the parent class.
Test this class in your test program from earlier. You'll notice that
everything works, even though we didn't define anything in Cylinder for radius.
This is because of the inheritance that allows Cylinder to implement code from Circle.
Method Overriding
When you create a child class, you may want to inherit methods from the
parent class as we did with the radius accessor and mutator. You can also
add methods and instance variables as we did with height and its accessor
and mutator methods. You can also modify methods that are inherited from
the parent class. This is referred to as
method overriding -- think
of it as overriding or creating a different version of an inherited method.
When you override a method, the method signature must remain the same as
the method in the parent class. The body of the method can be different
(and generally, that's the point). In Cylinder, the calcArea() method will
perform differently: it will calculate and return the cylinder's surface
area according to the following formula:
2 * PI * radius2 + height * 2 * PI * radius
You should have noticed that the PI * radius2 part of this
formula is actually the circle's area! This will become useful when we
code our method, as we can tell Cylinder's calcArea() to go and get the
result from Circle's calcArea()! To do this, you use the super keyword
to refer to the parent class's calcArea() method::
super refers to the Circle class definition, so this piece of the
statement is invoking the calcArea() method from Circle. You must include super
here, otherwise the calcArea() method will try to call itself (this is called
recursion).
What happens when you compile the Cylinder class now? You'll get the
following error:
Cylinder.java:33: radius has private access in Circle
return 2 * super.calcArea() * height * 2 * Math.PI * radius;
^
1 error
The radius variable is private in the Circle class -- you must use the
getRadius() method to access it!
1. Override toString() method, and add the
calcVolume() method in the Cylinder class. A cylinder's volume is its height
* the circle's area.
2. Add an equals() method to your classes. What makes two circles
equal? What would make two cylinder's equal?
Constructor Chaining
You might notice in your IDE when you have a constructor that calls
set-methods directly, you get a strange warning, "overridable method
call in constructor". What is that all about? What does it mean and how do we
fix it?
What is Constructor Chaining?
Constructor chaining occurs when you
construct an instance of a child class: before invoking the child class
constructor, first the parent class constructor is executed. If that parent
class has a parent class, then the parent's parent constructor executes
before the parent constructor. This continues right up the inheritance
chain
For example, imagine ClassC, which is a child of ClassB, and ClassB
is a child of ClassA. When you instantiate ClassC:
ClassC classc = new ClassC();
This calls the ClassC() constructor. Before any code inside the
constructor executes, the ClassB() constructor is invoked. Before
any of the code inside ClassB() executes, the ClassA() constructor
is invoked. ClassA is at the top of the inheritance chain, so the
ClassA() constructor's code executes.
When constructor ClassA() is finished executing, control moves
back to the ClassB() constructor, which finishes executing.
Once the ClassB() constructor is finished, then control moves
back to ClassC(), and the ClassC() constructor code executes.
The best way to truely understand constructor chaining
is with a practical demonstration:
Start a new project and add these 3 classes:
(The classes below are just examples and the docs are purposely omitted.
Also note that we NEVER print to the console inside a class - we only
do this here for demonstration purposes!)
public class Animal {
private String name = "unknown";
public Animal() {
System.out.println("Animal Default Constructor");
}
public Animal(String name) {
setName(name);
System.out.println(doStuff());
}
public void setName(String name) {
if (name != null && !name.trim().isEmpty())
this.name = name;
else
throw new IllegalArgumentException("Name can't be empty.");
}
public String getName() {
return name;
}
public String doStuff() {
return "Animal: " + name;
}
}
public class Pet extends Animal {
public String breed = "";
public Pet() {
System.out.println("Pet Default Constructor");
}
public Pet(String name, String breed) {
super(name);
setBreed(breed);
System.out.println(doStuff());
}
public void setBreed(String breed) {
this.breed = breed;
}
public String getBreed() {
return breed;
}
public String doStuff() {
return "Pet " + getName() + " is a " + breed;
}
}
public class Cat extends Pet {
public Cat() {
System.out.println("Cat Default Constructor");
}
public Cat(String name, String breed) {
super(name, breed);
}
}
In the Main Class for your project, add the following statement inside your main():
Cat cat = new Cat();
Now run your Main class. Note the output you get:
Animal Default Constructor
Pet Default Constructor
Cat Default Constructor
You can clearly see that the Animal default constructor executed,
then Pet default constructor, then the Cat default constructor.
Recall that unless you say otherwise, a call to super() (parent default
constructor) is automatic in every child constructor.
Program flow is as follows (follow along in the code as you read, you
can also watch the steps in action by using the Debug tools in NetBeans):
Our statement in main() is invoking the Cat() default constructor.
Before the code in Cat() executes, it first invokes super(), which
calls the Pet() constructor.
Before the code inside Pet() executes, it first invokes super(),
which calls the Animal() constructor.
The Animal class is the parent of our hierarchy, so the print
statement inside Animal() executes. Technically, Animal is a child
of the Object class, so the Animal() constructor does call super()
which executes the Object() constructor, but there's nothing
in Object() that we need to worry about.
Then Animal() is done, so program flow returns back to Pet().
The print statement inside Pet() executes, then program flow returns
to Cat(), where the print statement in Cat() executes.
Then the program returns to the main() method.
This is an example of constructor chaining!
Dynamic Binding
Dynamic Binding is a term that
describes how Java determines which method to execute when overridden or inherited
methods are called by looking to see what the actual type
of the calling object is. When you have an inheritance chain with overridden
or inherited methods, there might be multiple choices when a method call is made.
For example, if you have an instance of Animal:
Animal foo = new Animal();
...and then you want to invoke the doStuff() method, it should be clear that
calling foo.doStuff() will invoke the Animal class's doStuff()
method and not the Pet class's doStuff() method: that's because even though
Animal is a child of Pet, Java will invoke Animal's doStuff() because the
actual type of the foo object is Animal
However, you're also permitted to write a statement such as:
Animal bar = new Pet();
This constructs a Pet instance and references it with the Animal variable
bar. The actual type of bar
is Pet, but the declared type of bar
is Animal.
If you were to then try to execute bar.doStuff();, would Pet's doStuff()
execute or would Animal's doStuff() execute? Dynamic binding
states that we must look at what the actual type of the object is: in this
case the actual type is Pet, therefore the Pet's doStuff() method
will execute.
Overridable Method Call in Constructor Warning
In the Pet class, on line 13 (setBreed(breed); inside the single-param
constructor) you get the warning in the left margin "overrideable method
call in constructor". To understand what this means:
Comment out the previous exercise code in your project's main() and add this statement:
Pet pet = new Pet("Arti", "Tabby");
Run the Main project. Note the output after you run your Main class:
Pet Arti is a null
Pet Arti is a Tabby
Why do you get this output? The program flow is as follows (follow
along in the code as you read, you can also watch the steps in action
by using the Debug tools in NetBeans):
Pet(name, breed) constructor is invoked from inside main()
Pet(name, breed) constructor calls super(name), which is the Animal(name)
constructor.
Animal(name) constructor first calls setName(name), which sets the
name data member to "Arti".
Animal(name) invokes the doStuff() method.
This is where things get interesting!!
Due to dynamic binding,
the doStuff() method in the Pet class is invoked, not the one in Animal,
because the actual type of the object is Pet!
When Java sees that there is a call to doStuff(), it remembers that
we're actually in the process of creating a Pet, not an Animal, so
it knows that it needs to execute the version in Pet.
The Pet.doStuff() method returns the String "Pet Arti is a null"
back to the Animal(name) constructor. This is how that happens:
Pet.doStuff() concatenates "Pet " and getName() (which invokes
the getName() from Animal, and that returns the String "Arti").
setName() was called earlier in Step 3, so we know that name
contains "Arti".
Then Pet.doStuff() concatenates the literal " is a ", and then it
concatenates the breed member value.
However, breed has not been set, yet - that won't happen until Step 8
below. Therefore, breed is currently a null object. This is why the
string "null" gets concatenated onto the end of the string, giving us
"Pet Arti is a null".
Next, Animal(name) accepts the "Pet Arti is a null" string and sends
it to println(), which results in the first line of output.
Animal(name) is finished, and now control returns back to Pet(name, breed)
Pet(name, breed) calls setBreed(breed), which sets the breed member to "Tabby"
Then the doStuff() method is invoked. We're inside the Pet class now,
so again the Pet.doStuff() method is executed.
Pet.doStuff() now has a value in the breed member (step 8), so the String
"Pet Arti is a Tabby" is built and returned.
Pet(name, breed) accepts the "Pet Arti is a Tabby" string and sends it
to println(), which results in the second line of output.
The Pet constructor is finished; control returns to main(), which is
also finished.
This demonstrates why it's not recommended that you call methods in your
constructor that someone can override: it can cause bugs if those
methods are accessing child members that may not have been initialized, yet.
So, Solutions?
Be careful of the order in which you call overridable methods in a
constructor, or make sure those methods aren't accessing child members.
Perhaps in the Pet class constructor should not call doStuff() or maybe
doStuff() should not be accessing name unless it knows for sure it has
a value.
Prevent certain methods from being overridden by declaring them as
final. Perhaps we could make Animal.doStuff() final, and since we won't
be allowed to override it in Pet, use a different final method in Pet,
such as Pet.doThings().
The second option is the better solution and what most
programmers do. I use a private init() method, like this:
Methods defined as private or final can't be overriden in a
child class. Should you choose private methods or public final
methods? That depends:
Use private methods when you don't need your init() method to be
seen/used by programmers using your class. No one needs to use my
init() methods outside of Circle and Cylinder: if they want to set
radius and height to initialize a shape, they can construct a default
shape and then use setRadius() and setHeight().
Use public final methods when a programmer will need to use those methods
when they use your class. You would javadoc these methods normally.
Here's a simple example using public final init method:
public class CatCafe {
public static final String CAFE_NAME = "Wendi's Cat Cafe";
public static final int CHAIRS_PER_TABLE = 4;
private int numTables;
private int numChairs;
public CatCafe() {
// sets # of tables and chairs to default
init(10);
}
public CatCafe(int numTables) {
// set # of tables and chairs
init(numTables);
}
public final init(int numTables) throws IllegalArgumentException {
// make sure number of tables is valid
setNumTables(numTables);
// set #chairs if #tables is ok
numChairs = numTables * CHAIRS_PER_TABLE;
}
public void setNumTables(int num) {
if (num > 0)
numTables = num;
else
throw new IllegalArgumentException("Number of tables must be > 0.");
}
public int getNumTables() {
return numTables;
}
public int getNumChairs() {
return numChairs;
}
}
Exercises
1. Write a class called Sphere with the following specifications:
The surface area of a sphere is 4 * PI * radius2 (4 times circle area)
and the volume of a sphere is 4/3 * PI * r3, which could also be written
as 4/3 * radius * the circle area.
2. A kiosk for
Martin's Sweet Farm
at a farmer's market sells products that they make from
their honey bee farm. They sell honey, of course! But they also sell products
made from honey and bee's wax such as soap, lip balm, and candles. Many of their
products have flavours (which might actually be scents or colours, but they always
use the word "flavour"), for example they have Chili Honey (a delicious honey
with a chili pepper in the jar that infuses the honey with a nip of pepper taste!),
Strawberry-Mint Lip Balm, and Tea Tree Mint Honey Soap. They keep
track of inventory with a program that uses the following classes:
Honey is a Product, so Honey is an extension of Product.
Honey has 2 extra data members: type is the type of honey (e.g. Wildflower,
Buckwheat), and jarSize is the size of the jar (they sell currently sell
250g, 500g, and 700g jars, so a typical value would be 500.0).
All the string values in both classes are mandatory, they can't be
empty or null objects. Product name defaults to "Miscellaneous",
flavour defaults to "plain", and type defaults to "Wildflower".
The cost and the jar size must both be greater than 0. Cost
defaults to 2.5 and jar size defaults to 500.0.
The toString() for both classes returns the product name and the
cost in the following format:
Product Name: $x.xx
(cost formatted with a $ and to two decimal places)