Inheritance I

Recall that there are four basic mechanisms / ideas underlying object oriented programming:

  1. encapsulation
  2. information hiding
  3. inheritance
  4. polymorphism
Encapsulation (bundling together data and the functions that operate on that data) and information hiding (language mechanisms to enforce the separation of interface from implementation) "solved" the first of our three problems with Procedural Programming not giving us full separation of interface from implementation: the "what to do with structs" problem. We've covered both of these over the last few weeks.

Today we start to look at inheritance, which will solve the second of our three problems with Procedural Programming not giving us full separation of interface from implementation: that you can't modify or extend the behavior of functions or collections of functions and structs without messing around with their implementations. Inheritance is a mechanism that allows us to create a new class from an existing class. It has two basic kinds of uses: to modify or extend functionality, and to express commonality. We will focus on the first use for this lesson. The second use is intimately related to polymorphism, which is the last of the four basic mechanisms/ideas behind object oriented programming.

A problem to drive the discussion

Suppose there are existing classes Point and Triangle. We could assume for this discussion that you only have their .class files, i.e. that you can't even see their source code. I'm happy to let you see it, for educational purposes, but the point is that we could do everything we'll talk about today without ever having access to the source code. In any event, we'll assume we either are not allowed to change those two class definitions, or are unable to change them (no source code), or don't want to change them (too hard to figure out). We do, however, have their interfaces (shown below) and have used the class to create a cool little program Ex1.java that reads in four points, and returns the three points of the four that define the triangle with largest area. If you want the source code: see Point.java and Triangle.java.

Triangle Interface
public class Triangle {
  public static double perimeter(Point a, Point b, Point c);
  public static double area(Point a, Point b, Point c);
}
Point Interface
public class Point {
  public Point(double x, double y);
  public Point clone();
  public double getX();
  public double getY();
  public double getAngle();
  public double getRadius();
  public void subtractBy(Point p);
  public void multiplyBy(double d);
  public void rotateCCBy(double ang);
  public String toString();
  public static Point mkPointFromAngleRadius(double a, double r);
  public static Point read(Scanner sc);
}
Ex1.java
import java.util.*;
public class Ex1 {
  public static void main(String[] args) {
    // Read in four points
    Scanner sc = new Scanner(System.in);
    Point[] v = new Point[4];
    for(int i = 0; i < v.length; i++)
      v[i] = Point.read(sc);

    // Populate w with arrays for all three combinations
    // of three points from v
    Point[][]w = new Point[4][3];
    for(int i = 0; i < 4; i++) {
      for(int j = 0; j < 3; j++) {
        w[i][j] = v[j + (j < i ? 0 : 1)];
      }
    }

    // Find index m of the three points t[m][0],t[m][1],t[m][2]
    // that give the largest area
    int m = 0;
    for(int i = 1; i < 4; i++) {
      if (Triangle.area(w[m][0],w[m][1],w[m][2]) <
          Triangle.area(w[i][0],w[i][1],w[i][2]))
        m = i;
    }

    // Print out the three points that give the triangle with largest area
    System.out.println("The triangle with maximum area is: ");
    for(int i = 0; i < 3; i++)
      System.out.println(w[m][i].toString());
  }
}

Here's a sample run of the program:

~/$ java Ex1
1 2
2 3
3 4
4 5
The triangle with maximum area is:
1.0 2.0
3.0 4.0
4.0 5.0
So these are some nice classes; lots of functionality. Clearly the static methods area and perimeter in the Triangle class rely on some of the many methods of class Point, but we don't know which. Using these classes, we've easily written what is actually not an entirely trivial program.

Now: suppose our goal is to have the program work so that we enter a label with each point, and print out the labels of the three points that define the triangle with maximum area, like this:

~/$ java Ex3
1 2 P
2 3 W
3 4 Q
4 5 V
Triangle PQV has the greatest area.
This can be done, of course, in a variety of ways. So it's not that solving this little problem is all that difficult. What's interesting, is to see how the problem can be solved easily in an object oriented way, respecting the separation of interface and implementation, with the use of inheritance.

Inheritance: an "is-a" relationship rather than a "has-a"

What we would really like to do to solve our problem is to go add a "label" field to the class Point, so that when we had chosen the three points that defined the triangle of largest area, we could simply write out their labels. However, we can't modify the class Point: first, I said we couldn't, second, what about all those situations in which we want to have points without labels? So we have to create a new class that somehow combines a point and a label.

We already have a mechanism for using existing classes to build new classes. You use it whenever you create a class that has, for example, a String field (data member). We haven't given it a special name, but if you want to know, we call this composition of classes. If class Foo has two String fields, for instance, we would say that Foo is composed of two Strings. Composition is a has-a relationship. Foo "has-a" String field.

If we try to combine a point and a string label by composition, we get something like this:

public class LabPoint {
  private Point p;
  private String lab;
  ...
}
... which is depicted to the right. This really ugly for us. Why? Well this is no longer a point, which means that we can't just pass three LabPoints to Triangle.area anymore, but we constantly have to pull out the "point-part" of the LabPoint to pass around. In fact, this class has no methods yet at all, and we'd have to recreate all the methods we already have for Point in the new LabPoint class. Either that or, as with calling Triangle.area, we will have to constantly pull out the "point-part" whenever we want to calculate something. This is because a LabPoint defined this way has-a Point, rather than is-a Point.

If we approach this with inheritance, we would define the new class LabPoint as derived from the class Point — in Java class LabPoint extends Point — which means that a LabPoint object is-as Point, but it is a Point with some extra features. Thus, all the code that worked for Points, all the methods of class Point and methods that take Points as arguments, all still works. But we can add more! The existing class Point is called the "super-class" and the new class LabPoint is called the "sub-class".

Deriving a new class with extend — adding fields

Though it goes a bit against the grain to start with the implementation, in this case it's best to start with adding a field to store the label, as it makes what's going on a bit easier to see. To derive a new class LabPoint from class Point, and add a String label, we would write:

public class LabPoint extends Point {
  private String lab;
}

... which gives the picture shown to the right. Since a LabPoint is-a Point, if lp is a LabPoint we can make calls like lp.getRadius() or if lq is another LabPoint, calls like lp.subtractBy(lq). Why? Because lp and lq are Points. They're just Points that carry around labels. Now, before we do much of anything, though, we need to figure out how to initialize LabPoints when they're instantiated.

Deriving a new class with extend — constructors

Constructors require some discussion. Why? because the constructor for a LabPoint would naturally have three values - x, y, and label lab. However, the constructor has to split duties and say that we want the Point part of the LabPoint to be initialized with x and y, and the label part with lab. This is accomplished with the keyword super which, in this context, allows us to explicitly call the constructor for the "super-class" Point to initialize the Point part of the new object.

public class LabPoint extends Point {
  private String lab;

  public LabPoint(double x, double y, String lab) {
    super(x,y); //← calls Point constructor with arguments x and y
    this.lab = lab;
  }
}

Note: Java guarantees that a constructor for the superclass will be called whenever a new instance of the subclass is created. This means that if you don't explicitly call super(...) in the constructor, the compiler/JVM will implicitly execute super() prior to executing the statements in your constructor, where "super()" is a call to the superclass's "default constructor", i.e. the constructor that takes no arguments. If there is no accessible default constructor for the superclass (as indeed is the case with our Point example), it is an error.

Deriving a new class with extend — adding methods

In addition to extending Point by adding new fields, we can add new methods. In this case, it's natural to add a new method getLabel(), a method that wouldn't have made sense for class Point.

public class LabPoint extends Point {
  private String lab;

  public LabPoint(double x, double y, String lab) {
    super(x,y);
    this.lab = lab;
  }

  public String getLabel() { return lab; }
}

Deriving a new class with extend — overriding methods

Not only can we add new methods, we can modify the behavior of existing functions when applied to the new class LabPoint. This is called overriding a method. It's actually a special case of the function overloading we're all used to. If we add the following method definition to class LabPoint:

public String toString() {
  return getX() + " " + getY() + " " + getLabel();
}

then the call System.out.println(lp.toString()), where lp is a LabPoint, will result in LabPoint's version of toString() being called rather than Point's version of toString(), and what gets printed out will include the lp's label. Interestingly, we could also define this in terms of Point's version of toString() the following way:

public String toString() {
  return super.toString() + " " + getLabel();
}

... which says to return the string that Point's version of toString() would produce, concatenated with a space and the label.

We can also override static methods. This is slightly different, in the sense that the new static method really has a different name. In our example, we'll naturally want to override read(..) so that it reads in the label as well as the x and y value.

public static LabPoint read(Scanner sc) {
  Point tmp = Point.read(sc);
  if (tmp == null || ! sc.hasNext()) { return null; }
  String lab = sc.next();
  return new LabPoint(tmp.getX(),tmp.getY(),lab);
}
Once again, notice that we can make use of Point's versions of methods, as in the explicit call to Point.read. In the simple program below:
public static void main(String[] args) {
  Scanner sc = new Scanner(System.in);
  LabPoint p = LabPoint.read(sc);
  System.out.println(p.toString());
}
... notice how we specify which "read" we want, and notice that because p is LabPoint, we get the LabPoint version of toString() rather than the Point version.

Putting it all together

If you put these pieces of LabPoint together, you get the definition shown below. A simple search-and-replace of LabPoint for Point in the file Ex1.java yields a program that does almost exactly what we want. I massaged it slightly to produce the Ex3.java shown below, but that was to restrict the output too only the labels.

LabPoint.java

import java.util.*;

public class LabPoint extends Point {

  public LabPoint(double x, double y, String lab) {
    super(x,y);
    this.lab = lab;
  }

  public String getLabel() { return lab; }

  public static LabPoint read(Scanner sc) {
    Point tmp = Point.read(sc);
    if (tmp == null || ! sc.hasNext()) { return null; }
    String lab = sc.next();
    return new LabPoint(tmp.getX(),tmp.getY(),lab);
  }

  public String toString() {
    return getX() + " " + getY() + " " + lab;
  }

  private String lab;

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    LabPoint p = read(sc);
    System.out.println(p);
  }
}
Ex3.java

import java.util.*;
public class Ex3 {

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    // Read in four points
    LabPoint[] v = new LabPoint[4];
    for(int i = 0; i < v.length; i++) {
      v[i] = LabPoint.read(sc);
    }

    // Populate w with arrays for all three combinations
    // of three points from v
    LabPoint[][] w = new LabPoint[4][3];
    for(int i = 0; i < 4; i++) {
      for(int j = 0; j < 3; j++) {
        w[i][j] = v[j + (j < i ? 0 : 1)];
      }
    }

    // Find index m of the three points t[m][0],t[m][1],t[m][2]
    // that give the largest area
    int m = 0;
    for (int i = 1; i < 4; i++) {
      if (Triangle.area(w[m][0],w[m][1],w[m][2]) <
          Triangle.area(w[i][0],w[i][1],w[i][2]))
        m = i;
    }

    // Print out the three points that give the triangle with largest area
    System.out.print("Triangle ");
    for(int i = 0; i < 3; i++) {
      System.out.print(w[m][i].getLabel());
    }
    System.out.println(" has the greatest area.");
  }
}