Object-oriented programming allows you to define new classes from existing classes. This is called inheritance. The procedural paradigm focuses on designing methods and the object-oriented paradigm couples data and methods together into objects. Software design using the object-oriented paradigm focuses on objects and operations on objects. The object-oriented approach combines the power of the procedural paradigm with an added dimension that integrates data with operations into objects.
Inheritance is an important and powerful feature for reusing software. Suppose you need to define classes to model circles, rectangles, and triangles. These classes have many common features. What is the best way to design these classes so as to avoid redundancy and make the system easy to comprehend and easy to maintain? The answer is to use inheritance.
Superclasses and Subclasses
Inheritance enables you to define a general class (i.e., a superclass) and later extend it to more specialized classes (i.e., subclasses). You use a class to model objects of the same type. Different classes may have some common properties and behaviors, which can be generalized in a class that can be shared by other classes. You can define a specialized class that extends the generalized class. The specialized classes inherit the properties and methods from the general class.
Consider geometric objects. Suppose you want to design the classes to model geometric objects such as circles and rectangles. Geometric objects have many common properties and behaviors. They can be drawn in a certain color and be filled or unfilled. Thus a general class GeometricObject can be used to model all geometric objects. This class contains the properties color and filled and their appropriate getter and setter methods. Assume that this class also contains the dateCreated property and the getDateCreated() and toString() methods. The toString() method returns a string representation of the object. Since a circle is a special type of geometric object, it shares common properties and methods with other geometric objects. Thus it makes sense to define the Circle class that extends the GeometricObject class. Likewise, Rectangle can also be defined as a subclass of GeometricObject. Figure below shows the relationship among these classes. A triangular arrow pointing to the superclass is used to denote the inheritance relationship between the two classes involved.
In Java terminology, a class C1 extended from another class C2 is called a subclass, and C2 is called a superclass. A superclass is also referred to as a parent class or a base class, and a subclass as a child class, an extended class, or a derived class. A subclass inherits accessible data fields and methods from its superclass and may also add new data fields and methods.
The Circle class inherits all accessible data fields and methods from the GeometricObject class. In addition, it has a new data field, radius, and its associated getter and setter methods. The Circle class also contains the getArea(), getPerimeter(), and getDiameter() methods for returning the area, perimeter, and diameter of the circle.
The Rectangle class inherits all accessible data fields and methods from the GeometricObject class. In addition, it has the data fields width and height and their associated getter and setter methods. It also contains the getArea() and getPerimeter() methods for returning the area and perimeter of the rectangle.
The GeometricObject, Circle, and Rectangle classes are shown in the programs (a), (b), and (c). We’ll name these classes SimpleGeometricObject, CircleFromSimpleGeometricObject, and RectangleFromSimpleGeometricObject in this chapter. For simplicity, we will still refer to them in the text as GeometricObject, Circle, and Rectangle classes. The best way to avoid naming conflicts is to place these classes in different packages. However, for simplicity and consistency, all classes in this book are placed in the default package.
(a)
package demo;
public class SimpleGeometricObject {
private String color = "white";
private boolean filled;
private java.util.Date dateCreated;
/** Construct a default geometric object */
public SimpleGeometricObject() {
dateCreated = new java.util.Date();
}
/** Construct a geometric object with the specified color and filled value */
public SimpleGeometricObject(String color, boolean filled) {
dateCreated = new java.util.Date();
this.color = color;
this.filled = filled;
}
/** Return color */
public String getColor() {
return color;
}
/** Set a new color */
public void setColor(String color) {
this.color = color;
}
/** Return filled. SInce filled is boolean, its getter method is named isFIlled */
public boolean isFIlled() {
return filled;
}
/** Set a new filled */
public void setFilled(boolean filled) {
this.filled = filled;
}
/** Get dateCreated */
public java.util.Date getDateCreated() {
return dateCreated;
}
/** Return a string representation of this object */
public String toString() {
return "created on " + dateCreated + "\ncolor: " + color + " and filled: " + filled;
}
}
(b)
package demo;
public class CircleFromSimpleGeometricObject extends SimpleGeometricObject {
private double radius;
public CircleFromSimpleGeometricObject() {}
public CircleFromSimpleGeometricObject(double radius) {
this.radius = radius;
}
public CircleFromSimpleGeometricObject(double radius, String color, boolean filled) {
this.radius = radius;
setColor(color);
setFilled(filled);
}
/** Return radius */
public double getRadius() {
return radius;
}
/** Set a new radius */
public void setRadius(double radius) {
this.radius = radius;
}
/** Return area */
public double getArea() {
return radius * radius * Math.PI;
}
/** Return diameter */
public double getDiameter() {
return 2 * radius;
}
/** Return perimeter */
public double getPerimeter() {
return 2 * radius * Math.PI;
}
/** Print the circle info */
public void printCircle() {
System.out.println("The circle is created " + getDateCreated() + " and the radius is " + radius);
}
}
The Circle class (program (b)) extends the GeometricObject class (program (a)) using the following syntax:
The keyword extends (lines 3) tells the compiler that the Circle class extends the GeometricObject class, thus inheriting the methods getColor, setColor, isFilled, setFilled, and toString.
The overloaded constructor Circle(double radius, String color, boolean
filled) is implemented by invoking the setColor and setFilled methods to set the color and filled properties (lines 12–16). These two public methods are defined in the superclass GeometricObject and are inherited in Circle, so they can be used in the Circle class.
You might attempt to use the data fields color and filled directly in the constructor as follows:
public CircleFromSimpleGeometricObject(
double radius, String color, boolean filled) {
this.radius = radius;
this.color = color; // Illegal
this.filled = filled; // Illegal
}
This is wrong, because the private data fields color and filled in the GeometricObject class cannot be accessed in any class other than in the GeometricObject class itself. The only way to read and modify color and filled is through their getter and setter methods.
The Rectangle class (program (c)) extends the GeometricObject class (program (a)) using the following syntax:
The keyword extends (lines 3) tells the compiler that the Rectangle class extends the GeometricObject class, thus inheriting the methods getColor, setColor, isFilled, setFilled, and toString.
(c)
package demo;
public class RectangleFromSimpleGeometricObject extends SimpleGeometricObject {
private double width;
private double height;
public RectangleFromSimpleGeometricObject() {}
public RectangleFromSimpleGeometricObject(double width, double height) {
this.width = width;
this.height = height;
}
public RectangleFromSimpleGeometricObject(double width, double height, String color, boolean filled) {
this.width = width;
this.height = height;
setColor(color);
setFilled(filled);
}
/** Return width */
public double getWidth() {
return width;
}
/** Set a new width */
public void setWidth(double width) {
this.width = width;
}
/** Return height */
public double height() {
return height;
}
/** Set a new height */
public void setHeight(double height) {
this.height = height;
}
/** Return area */
public double getArea() {
return width * height;
}
/** Return perimeter */
public double getPerimeter() {
return 2 * (width + height);
}
}
The code in program below creates objects of Circle and Rectangle and invokes the methods on these objects. The toString() method is inherited from the GeometricObject class and is invoked from a Circle object (line 5) and a Rectangle object (line 13).
Note the following points regarding inheritance:
- Contrary to the conventional interpretation, a subclass is not a subset of its superclass. In fact, a subclass usually contains more information and methods than its superclass.
- Private data fields in a superclass are not accessible outside the class. Therefore, they cannot be used directly in a subclass. They can, however, be accessed/mutated through public accessors/mutators if defined in the superclass.
- Not all is-a relationships should be modeled using inheritance. For example, a square is a rectangle, but you should not extend a Square class from a Rectangle class, because the width and height properties are not appropriate for a square. Instead, you should define a Square class to extend the GeometricObject class and define the side property for the side of a square.
- Inheritance is used to model the is-a relationship. Do not blindly extend a class just for the sake of reusing methods. For example, it makes no sense for a Tree class to extend a Person class, even though they share common properties such as height and weight. A subclass and its superclass must have the is-a relationship.
- Some programming languages allow you to derive a subclass from several classes. This capability is known as multiple inheritance. Java, however, does not allow multiple inheritance. A Java class may inherit directly from only one superclass. This restriction is known as single inheritance. If you use the extends keyword to define a subclass, it allows only one parent class. Nevertheless, multiple inheritance can be achieved through interfaces.