Interfaces and Inheritance: Commentary

Steven Zeil

Last modified: Aug 19, 2016
Contents:

There are two ways to approach inheritance. We can treat it as yet another programming language construct, studying what it looks like and how it works and trusting to the programmer to use it, like functions, loops, etc., in the circumstances where it is appropriate. Or we can view it as a fundamental relationship between classes in the real world and talk abut how to use it in programming to model those relationships.

The “fundamental relationship” approach is actually the province of CS 330, Object-Oriented Programming and Design. In that class, we explore why inheritance is important and how it contributes to an overall style of object-oriented programming.

But CS382 is a programming language class, and so we will focus on inheritance as a programming language construct. Even from that limited point of view, we’ll see that inheritance can offer some value in terms of code reuse and flexibility. I’ll continue to point out parallels to C++, although if you have not taken CS330, you may not have encountered the relevant C++ constructs yet.

1 Interfaces

C++ has no direct equivalence to the Java interface. The closest equivalent would be a purely abstract class:

class OperateCar {
public:
   // constant declarations, if any

   // method signatures
   //
   // An enum with values RIGHT, LEFT
   virtual 
   int turn(Direction direction,
			double radius,
			double startSpeed,
			double endSpeed) = 0;
			
   virtual
   int changeLanes(Direction direction,
			       double startSpeed,
			       double endSpeed) = 0;

   virtual
   int signalTurn(Direction direction,
                  bool signalOn) = 0;

   virtual
   int getRadarFront(double distanceToCar,
                     double speedOfCar) = 0;

   virtual
   int getRadarRear(double distanceToCar,
                    double speedOfCar) = 0;
      ⋮
   // more method signatures
};

in which none of the function members provide actual bodies.

But, as we will see, Java has abstract classes as well. So why does Java need both abstract classes and interfaces? The answer is at the bottom of the tutorial page. C++ permits multiple inheritance. A C++ class can inherit from multiple base classes, thereby picking up the union of all of their members.

But multiple inheritance is complicated and messy (both for programmers and compilers) and the Java designers made an early decision to avoid it. But there are many practical situations where multiple inheritance seems to be the reasonable thing to do.

Java’s interfaces add just enough limitations to the idea of an abstract class that “inheriting” from multiple interfaces does not encounter the same difficulties as inheriting from multiple classes.

1.1 Defining an Interface

1.2 Implementing an Interface

A Sample Interface, Relatable

The closest equivalent in C++:

class Relatable {
public:
   // this (object calling isLargerThan)
   // and other must be instances of 
   // the same class returns 1, 0, -1 
   // if this is greater // than, equal 
   // to, or less than other
   virtual int isLargerThan(const Relatable& other) const = 0;
}

Implementing the Relatable Interface

Again, the C++ equivalent:

class RectanglePlus : public Relatable {
public:
   int width = 0;
   int height = 0;
   Point origin;

   // four constructors
   RectanglePlus() {
      origin = new Point(0, 0);
   }
   
   RectanglePlus(Point p) {
      origin = p;
   }
   
   RectanglePlus(int w, int h) {
      origin = new Point(0, 0);
      width = w;
      height = h;
   }
   
   RectanglePlus(Point p, int w, int h) {
      origin = p;
      width = w;
      height = h;
   }

   // a method for moving the rectangle
   void move(int x, int y) {
      origin.x = x;
      origin.y = y;
   }

   // a method for computing
   // the area of the rectangle
      int getArea() const {
      return width * height;
   }

   // a method required to implement
   // the Relatable interface
   int isLargerThan(const Relatable& other) const {
      RectanglePlus& otherRect 
         = (RectanglePlus&)other;
      if (this->getArea() < otherRect.getArea())
         return -1;
      else if (this->getArea() > otherRect.getArea())
         return 1;
      else
         return 0;               
   }
};

(Actually, we would probably not keep all the function bodies within the class declaration in C++, as there is no reason to make all of these inline.)

1.3 Using an Interface as a Type

The code on this page is written this way to make a point. In fact, no good Java programmer would declare findLargest and findSmallest this way and then use type casts to Relatable. Instead, they would write:

public Object findLargest(
     Relatable obj1,
     Relatable obj2) {
   if ( (obj1).isLargerThan(obj2) > 0)
      return object1;
   else 
      return object2;
}

public Object findSmallest(
     Relatable obj1,
     Relatable obj2) {
   if ( (obj1).isLargerThan(obj2) < 0)
      return object1;
   else 
      return object2;
}

because people using these functions should not be expected to read the function bodies in order to find out that they can only be used with parameters that are Relatable.

1.4 Rewriting Interfaces

1.5 Summary of Interfaces

2 Inheritance

Inheritance in C++ and Java are very similar in concept, syntax, and behavior.

But one of the most important differences between the two languages lies in the existence of a basic class hierarchy.

In Java, there is a single inheritance tree that contains all classes, both those provided by the Java API and those that you have written or might write in the future. If you create a new Java class and don’t explicitly make it inherit from some base class, then implicitly it inherits from java.lang.Object. So your class will, no matter what you do, be linked in somewhere in the Java inheritance tree.

By contrast, C++ has no predefined inheritance tree. Inheritance is used more sparingly, and only for specific purposes within an application.

The inheritance tree in Java is tall. Look, for example, at the Java API description of JButton, a class for representing a simple button in a user interface. Near the top, you can see the chain of inheritance steps leading from Object to JButton. JButton inherits from AbstractButton, which inherits from JComponent, which inherits from Container, which inherits from Component, which inherits from Object. That’s not particularly unusual in Java. By contrast, most C++ inheritance trees seldom exceed 2 or 3 steps.

The inheritance trees in both Java and C++ can be broad. Inheritance is often used in contexts where we need to provide lots of variant implementations of a behavior. For example, a GUI library would probably have all the possible controls that can occur in a window or dialog box as subclasses of a base class that defines an interface for drawing controls at a given screen position and for responding to mouse clicks. (That is, in fact, the purpose of the Component class mentioned above).

The upshot is that inheritance in Java is like a banyan tree - tall and broad, with branches reaching over a vast area.

Inheritance in C++ is more like a handful of small shrubs bordering a garden.

An Example of Inheritance

The C++ equivalent:

class Bicycle {
public:        
   // the Bicycle class has
   // three fields
   int cadence;
   int gear;
   int speed;

   // the Bicycle class has
   // one constructor
   Bicycle(int startCadence,
           int startSpeed,
           int startGear) {
	  gear = startGear;
	  cadence = startCadence;
	  speed = startSpeed;
   }

   // the Bicycle class has
   // four methods
   void setCadence(int newValue) {
      cadence = newValue;
   }

   void setGear(int newValue) {
      gear = newValue;
   }

   void applyBrake(int decrement) {
      speed -= decrement;
   }

   void speedUp(int increment) {
      speed += increment;
   }

};

from which we could inherit like this:

class MountainBike : public Bicycle {
public:       
   // the MountainBike subclass adds
   // one field
   public int seatHeight;

   // the MountainBike subclass has one
   // constructor
   public MountainBike(int startHeight,
                       int startCadence,
					   int startSpeed,
					   int startGear)
   : Bicycle(startCadence, startSpeed, startGear)
   {
      seatHeight = startHeight;
   }   

   // the MountainBike subclass adds
   // one method
   public void setHeight(int newValue) {
      seatHeight = newValue;
   }
};

2.1 Overriding and Hiding Methods

2.2 Polymorphism

CS330 students will know that polymorphism (more properly, dynamic binding) takes place in C++ only if the function was declared as virtual:

class Bicycle {
public:        
   // the Bicycle class has
   // three fields
   int cadence;
   int gear;
   int speed;

   // the Bicycle class has
   // one constructor
   Bicycle(int startCadence,
           int startSpeed,
		   int startGear) {
      gear = startGear;
      cadence = startCadence;
      speed = startSpeed;
   }

   // the Bicycle class has
   // four methods
   void setCadence(int newValue) {
      cadence = newValue;
   }

   void setGear(int newValue) {
      gear = newValue;
   }

   void applyBrake(int decrement) {
      speed -= decrement;
   }

   void speedUp(int increment) {
      speed += increment;
   }

   virtual void printDescription() const;

};

void Bicycle::printDescription(){
   cout << "\nBike is "
		<< "in gear " << this.gear
		<< " with a cadence of "
		<< this.cadence
		<< " and travelling at a speed of "
		<< this.speed << ". " << endl;
}

and then supplied a variant implementation in a subclass:

class MountainBike : public Bicycle {
public:       
   string suspension;

   // the MountainBike subclass has one
   // constructor
   public MountainBike(int startCadence,
                       int startSpeed,
					   int startGear,
					   string suspensionType)
   : Bicycle(startCadence, startSpeed, startGear)
   {
      setSuspension(suspensionType);
   }

   string getSuspension() const{
      return suspension;
   }

   void setSuspension(string suspensionType){
      suspension = suspensionType;
   }

   virtual void printDescription() const;
};

void MountainBike::printDescription()
{
   Bicycle::printDescription();
   cout << "The " <<
        "MountainBike has a" <<
		getSuspension() <<
		" suspension." << endl;
}

2.3 Hiding Fields

2.4 Using the Keyword super

C++ does not have the “super” keyword. There’s a good reason for this. C++ allows multiple inheritance where a class inherits from two or more base classes, so the idea that “super” would refer to a unique class simply isn’t valid in C++.

On the other hand, C++ can achieve the same effects by explicitly naming the base class:

class MountainBike : public Bicycle {
public:       
   string suspension;

   // the MountainBike subclass has one
   // constructor
   public MountainBike(int startCadence,
                       int startSpeed,
					   int startGear,
					   string suspensionType)
   : Bicycle(startCadence, startSpeed, startGear)
   {
      setSuspension(suspensionType);
   }

   string getSuspension() const{
      return suspension;
   }

   void setSuspension(string suspensionType){
      suspension = suspensionType;
   }

   virtual void printDescription() const;
};

void MountainBike::printDescription()
{
   Bicycle::printDescription();
   cout << "The " <<
		"MountainBike has a" <<
		getSuspension() <<
		" suspension." << endl;
}

2.5 Object as a Superclass

This is a major difference between C++ and Java. Because every class in Java is, at the very least, inheriting from Object, there’s quite a few methods already declared for any new class you create in Java, even before you start writing any of your own methods.

The clone() Method

Programmers who develop a C++ inheritance hierarchy for their own application will often add a clone() function. It’s a useful thing to have.

The equals() Method

Note that Java does not allow programmers to overload operators. A C++ programmer would probably have overloaded operator== for this purpose. But in Java, == compares the addresses of objects rather than the data contained in those objects. Hence C++ programmers need to get into the habit of calling equals() instead.

The finalize() Method

This is the equivalent to a destructor in C++. Now, at least 90% of the time, a C++ programmer uses a destructor to delete pointers as part of the cleanup when an object is about to be destroyed. In Java, we don’t delete pointers, but rely on the automatic garbage collector to do that kind of cleanup. So there’s comparatively little need for programmer supplied cleanup in Java.

On the other hand, it’s impossible to predict when (or even if) a given object will be finalize()’d. So if you need to clean up after objects that are no longer needed, finalize() is a bit riskier.

For example, many C++ programmers would be comfortable writing this:

string readFromFile (string fileName)
{
   ifstream in (fileName);
   string line;
   getline (in, line);
   return line;
}

knowing that the compiler actually treats this as

string readFromFile (string fileName)
{
   ifstream in (fileName);
   string line;
   getline (in, line);
   in.~ifstream(); // destructor call generated by compiler
   return line;
}

and that the ifstream destructor closes the file if it is still open.

A Java programmer can’t afford that luxury. The file needs to be closed:

public String readFromFile (String fileName) 
throws FileNotFoundException, IOException
{
   BufferedReader in = new BufferedReader (new FileReader (fileName));
   String line = in.readLine();
   in.close();
   return line;
}

Although the finalize() method for FileReader may eventually close it, that might not get called for a very long time, possibly not until the program terminates. In fact, there is no real guarantee that finalizers will be called prior to the program termination.

The getClass() Method

The closest C++ equivalent, though not in very common use, is the typeid operator, which can be used to determine if two objects are of the same type of if an object inherits from a specific type.

The hashCode() Method

Many of the utility data structures in java.util are based on hashing, and so require classes to provide a suitable implementation of this function. By contrast, many of the utility data structures provided in the C++ std library are based upon binary search trees, and therefore require a suitable implementation of the < operator. Programmers in each language just need to get in the habit of providing these whenever possible.

The toString() Method

Savvy C++ programmers quickly learn to provide an output operator (operator<<) for every class they write, if only to aid in debugging. But Java does not have an output “operator” and would not permit programmers to overload it if it did. The Java I/O classes do not lend themselves to extension to outputting new classes. So, in Java, the workaround is to recognize that the output classes can write strings, so savvy Java programmers will always provide this function to convert their classes’ internal data into a string.

We’ve also seen in earlier labs that this function is exploited by automatic debuggers as the preferred way to display data during debugging.

2.6 Writing Final Classes and Methods

There is no direct equivalent to this in C++.

2.7 Abstract Methods and Classes

Abstract classes in C++ are very similar, but they are not labelled as abstract. Instead, a C++ class is abstract if any of its methods are abstract. A method is marked as abstract using “=0” at the end:

class GraphicObject {
   int x, y;
     ⋮
   virtual void moveTo(int newX, int newY) {
     ⋮
   }
   virtual void draw() = 0;
   virtual void resize() = 0;
};

2.8 Summary of Inheritance