Hierarchies for Inheritance#

Consider our example from the last chapter, where we created the class Square. Suppose now we add the class Rectangle, which we characterize with the data attributes length and width and also include the methods calcArea and calcPerimeter. We might make a file such as this one on the left (remembering our file for Squares, shown on the right):

rectangle
public class Rectangle {
   private double length;
   private double width;

   public Rectangle(double length, double width){
      this.length = length;
      this.width = width;
   }

   public double calcArea(){
      return length * width;
   }

   public double calcPerimeter(){
      return length * 2 + width * 2;
   }
}//class Rectangle
square
public class Square {
   private double length;

   public Square(double length){
      length = length;
   }

   public double calcArea(){
      return length * length;
   }

   public double calcPerimeter(){
      return length * 4;
   }
}//class Square

There is a lot of repetition in the code above. Really, a square is just a special kind of rectangle, so if we were to set the width of a square equal to its length (by adding that instance variable), then the methods for calculating area and perimeter could be identical. Could we avoid repeating their code inside the Square class? The answer is “yes.” As proficient Computing Scientists, we want to have a shorter amount of code where possible – less code to read as we continue developing our project.

Factoring into Superclass#

The idea of a square being a special kind of rectangle is handled in Java by creating a hierarchy, using the Java reserved word extends. We want to create a picture as follows, where part a) shows the simplistic hierarchy and part b) shows the hierarchy with the data fields and methods:

square

a. Collapsed

square

b. Expanded

This picture shows that a square is a special kind of a rectangle, that is, a square is a specialization of a rectangle. A square inherits attributes from a rectangle, and not the other way around. In the diagram, the arrow shows the path from Square to Rectangle. If we have a square we can move upwards to find attributes in a rectangle that may apply. We cannot move downward from a rectangle into a square. A rectangle is more general than a square and cannot be inherited from a square because assuming equal length and width will not work for some rectangles. We say that Rectangle is a superclass of Square. Alternatively, Square is a subclass of Rectangle.

Here is the new Java code creating the hierarchical relationship between a rectangle and a square, and factoring the code so that calcArea and calcPerimeter are only in one place, namely inside the class Rectangle:

square
public class Rectangle {
   protected double length;
   protected double width;

   public Rectangle(double length, double width){
      this.length = length;
      this.width = width;
   }

   public double calcArea(){
      return length * width;
   }

   public double calcPerimeter(){
      return length * 2 + width * 2;
   }
}//class Rectangle	
square
public class Square extends Rectangle{
   public Square(double length){
      super(length, length);
   }
}//class Square

See how short our class Square has become. Objects of type square will inherit the data fields (length and width) from rectangles and also the methods to find area and perimeter. Note though that it is important to create a constructor for squares, which calls the constructor of a rectangle (by the use of the super method and not by the usual constructor name). This constructor ensures that length and width are set to the same value. When we run the main program we created in the multiple files section of Chapter 2, the results are identical even though our file structure now includes inheritance.

Short Code is (Often) Better

The most important thing in writing code is correctness. After that, there are other characteristics of code that are important. One is readability, and shorter code is usually easier to read.

The access modifiers on length and width have been changed to protected, to allow the code in Square to have direct access to them. In the code written so far, we could have left that access private (since we have never directly accessed length or width inside the class for Square), but coming up we will use protected access.

Let’s look at these lines of code again, where a Square is created and then used:

Square mirror = new Square(5);      
System.out.println("mirror area: " + mirror.calcArea());
object mirror shown in memory, with length and width fields

During execution, when the object mirror is made, the constructor for a Square is called. This constructor, in turn, calls the constructor of the superclass, that is, the constructor of the class for Rectangle. This constructor is called with the length of the square becoming both the length and width of the rectangle. Hence an object, an instance of Square, with size 5 X 5 is created in memory. The name of this object is mirror.

look for calcArea in Square then move up and look in Rectangle

In the print statement, the dot message calcArea() is sent to mirror. The computer first looks at the level of a Square, since mirror is a Square, but no such method is found. This means that the hierarchy must be used. Moving up one level, the calcArea() method is found, in class Rectangle. This method is now run to produce our result of \(25.0\).





Practice Questions#

  1. Create a way to determine the length of the diagonal of a square and of a rectangle.

  2. Create a new Square of size 8 X 8 and print the length of its diagonal.

  3. Create a new Rectangle of size 5 X 3 and print the length of its diagonal.

To Solutions