Inheritance

The remaining concepts are somewhat more complicated, but they provide the nuts and bolts of any object-oriented programming language. While it is possible that in tiny programs these concepts are not necessary, they are essential for programming with a graphical user interface.

Inheritance

Inheritance is a language construct that allows for classes to be related to one another so that one class can inherit features of another class. The class or classes inherited from are called superclasses, or ancestors. The inheriting classes are called subclasses, or descendents. Inheritance is indicated using the keyword extends.

A subclass automatically possesses all of the superclass methods and fields except those marked private in the superclass.

A subclass can access a superclass method or field by prefacing it with the keyword super and the superclass constructor is available using the super(parameter list) method.

The true value of inheritance will become clear when we start making extensive uses of standard Java core classes, especially those contained in the Abstract Window Toolkit (awt). For now, it is not always obvious why inheritance makes programming more efficient. However, at the very least it makes for esthetical code that resembles somewhat the true relationship between objects. Also, inheritance will usually result in a reduced number of lines of code, as well as in objects that are more likely to work correctly and are easier to fix if they do not.
 
Example:

Suppose we need to write a program that deals with the area and perimeter of geometric shapes such as rectangles and circles, and possibly other shapes as well. Design these classes, exploring the possibly hierarchies between them. Provide full implementations of all methods.

Rather than creating different, unrelated classes for each of these objects, we first try to explore whether there is some relationship between them. Actually, the first sentence in the example already indicates that relationship: both rectangle and circles are geometric shapes. Moreover, all geometric shapes have a name, an area, and a perimeter. Therefore, we first design a class Shape that will serve as our superclass. We will declare the class as being public, which implies that it needs to be saved in a file named "Shape.java".

public class Shape
{ // fields 
   String name; 
   double area, perimeter; 
   // constructor 
   public Shape() 
   { 
      name = "undetermined"; 
      area = perimeter = 0; 
   } 
   // methods 
   public void display() 
   { 
      System.out.println("Name: " + name); 
      System.out.println("Area: " + area); 
      System.out.println("Perimeter: " + perimeter); 
   } 
}

Since we don’t know the actual shape, we can not compute appropriate values for the area and perimeter yet. Now, a rectangle and a circle are both more specific shapes. Hence, we want to indicate that relationship when defining these new classes by stating that both circles and rectangles are descendents, or subclasses, or the Shape class. In other words:

public class Rectangle extends Shape
{ // implementation } 
public class Circle extends Shape 
{ // implementation }

Thus, Rectangle and Circle are both defined as subclasses of Shape, and therefore they inherit the fields and methods of that superclass. Note that both fields in Shape do not have an access modifier and are therefore friendly fields. As such, they can be inherited and accessed by the Rectangle and Circle class. Had we designated the area and perimeter fields of Shape as private they would not be inherited by any subclass.

Here is a possible implementation of the Rectangle class:

public class Rectangle extends Shape 
{  // fields 
   double length, width; 
   // constructors
   public Rectangle()
   {
      name = "Rectangle";
      length = width = 0;
   }
   public Rectangle(double _length, double _width) 
   { 
      name = "Rectangle"; 
      length = _length; 
      width = _width; 
   } 
   // methods 
   public void computeArea() 
   { 
      area = length * width; 
   } 
   public void computePerimeter() 
   { 
      perimeter = 2*(length + width); 
   } 
}

Note that the Rectangle class can use the name, perimeter and area fields even though it does not explicitly declare them. However, since Rectangle extends Shape, it inherits those fields, and can therefore use them as if they were its own. Since Rectangle is public, we must save it in a file named "Rectangle.java".

Similarly, we can implement the Circle class as follows (recall that the area of a circle is  and the circumference, or perimeter, is , where r is the radius):

public class Circle extends Shape 
{  // fields 
   double radius; 
   // constructors
   public Circle()
   {
      name = "Circle;
      radius = 0;
   }
   public Circle(double _radius) 
   { 
      name = "Circle"; 
      radius = _radius; 
   } 
   // methods 
   public void computeArea() 
   { 
      area = Math.PI * radius * radius; 
   } 
   public void computePerimeter() 
   { 
      perimeter = 2 *Math.PI * radius; 
   }
}

Since the Rectangle and Circle classes inherit the name, area and perimeter field from Shape, they do not need to explicitly declare those fields again. In fact, if they did declare them again, they would hide the original fields of the Shape class (see below). In addition, all shapes can make use of the display method of the superclass, without having to define it again in each new actual shape.

To test these classes, here is a sample program (to be saved in a file named "ShapeTester.java"):

public class ShapeTester 
{ 
   public static void main(String args[]) 
   { 
      Shape s = new Shape(); 
      Rectangle r = new Rectangle(2.0, 3.0); 
      Circle c = new Circle(4.0);  

      r.computeArea(); 
      r.computePerimeter(); 
      c.computeArea(); 
      c.computePerimeter(); 
      r.display(); 
      c.display(); 
      s.display(); 
   } 
}

One of the advantages of inheritance is that it is easy to add new classes.
 
Example:

Add a Square class to the above collection of shapes.

As before, a Square extends Shape, inheriting its fields and methods:

public class Square extends Shape 
{  // fields 
   double side; 
   // constructors
   public Square()
   {
      name = "Square";
      side = 0;
   }
   public Square(double _side) 
   { 
      name = "Square"; 
      side = _side; 
   } 
   // methods 
   public void computeArea() 
   { 
      area = side * side; 
   } 
   public void computePerimeter() 
   { 
      perimeter = 4*side; 
   }

Actually, another approach seems better: why not say that a square is a special case of a rectangle - which it is, after all. In other words, our new class implementation would start out as:

public class Square extends Rectangle 
{ // implementation }

While that sounds like the right approach, there is a slight problem, but we have no need right now to discuss that. Instead, if you follow the remedies below, you will never see the problem:

Every class extending another class should always include an explicit call to one of the constructors of its superclass with the appropriate parameters. That call to the superclass constructor must appear as the first line in the subclass constructor.
Provide a constructor with no arguments for any classes that may become a superclass. If additional constructors are needed, simply use the concept of overloading to define them as well.

 Now let’s return to the previous example of a Square class that extends Rectangle instead of Shape.

Example:

Add a Square class to the previous collection of shapes in such a way that Square extends Rectangle instead of Shape.

The new class will be extremely short, yet it will have the same capabilities as the other subclasses of Shape:

public class Square extends Rectangle 
{  // constructor 
   public Square(double _side) 
   { 
      super(_side, _side); 
      name = "Square"; 
   } 
}

(bgw)