Multiple Inheritance, Interfaces, and Abstract Classes

Steven Zeil

Last modified: Oct 31, 2018
Contents:

 

Look at this set of classes.

It expresses a set of ideas familiar to most college students.

1 Multiple Inheritance

C++ supports this kind of model directly via multiple inheritance. A class may inherit directly from multiple parents:

class UniversityPersonnel {
   ⋮
public:
   string getName() const; 
   Address getAddress() const; 
   long getID() const; 
};

class Teacher: public UniversityPersonnel {
   ⋮
public:
    Department getDept() const;   
    Money getSalary();
    void pay(Money amount);
};

class Student: public UniversityPersonnel {
    ⋮
public:
    Program getMajor() const;
    int getQualityPoints() const;
    int getCredits() const;
    double gpa() const;
};

class GTA: public Teacher, public Student {
   ⋮
};

    ⋮
UniversityPersonnel up;
Student s;
Teacher t;
GTA gta;
⋮

Question: Which of the following calls is legal?

string nm1 = up.getName();
string nm2 = s.getName();
string nm3 = t.getName();
string nm4 = gta.getName();

Money m1 = up.getSalary();
Money m2 = s.getSalary();
Money m3 = t.getSalary();
Money m4 = gta.getSalary();

double d1 = up.gpa();
double d2 = s.gpa();
double d3 = t.gpa();
double d4 = gta.gpa();
Answer

Many programmers and designers prefer to avoid multiple inheritance in C++ when possible, because they consider it confusing and more trouble than it is really worth.

2 Interfaces in Java

The designers of Java definitely felt that multiple inheritance was too messy to be worthwhile. Instead, Java offers an alternate, closely related, mechanism for relating classes to one another, the interface.

2.1 Examples

2.1.1 AudioClip

AudioClip.java
public interface AudioClip { 
    /** 
     * Starts playing this audio clip. Each time this method is called,  
     * the clip is restarted from the beginning.  
     */ 
    void play(); 
 
    /** 
     * Starts playing this audio clip in a loop.  
     */ 
    void loop(); 
 
    /** 
     * Stops playing this audio clip.  
     */ 
    void stop(); 
} 

This describes pretty much the minimal functionality your would need to be a useful audio clip.

2.1.2 Cloneable

Signals that a class has a working clone() function.

2.1.3 Iterators in Java

Iterator.java
/*
 * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.util;

/**
 * An iterator over a collection.  {@code Iterator} takes the place of
 * {@link Enumeration} in the Java Collections Framework.  Iterators
 * differ from enumerations in two ways:
 *
 * <ul>
 *      <li> Iterators allow the caller to remove elements from the
 *           underlying collection during the iteration with well-defined
 *           semantics.
 *      <li> Method names have been improved.
 * </ul>
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @param <E> the type of elements returned by this iterator
 *
 * @author  Josh Bloch
 * @see Collection
 * @see ListIterator
 * @see Iterable
 * @since 1.2
 */
public interface Iterator<E> {
    /**
     * Returns {@code true} if the iteration has more elements.
     * (In other words, returns {@code true} if {@link #next} would
     * return an element rather than throwing an exception.)
     *
     * @return {@code true} if the iteration has more elements
     */
    boolean hasNext();

    /**
     * Returns the next element in the iteration.
     *
     * @return the next element in the iteration
     * @throws NoSuchElementException if the iteration has no more elements
     */
    E next();

    /**
     * Removes from the underlying collection the last element returned
     * by this iterator (optional operation).  This method can be called
     * only once per call to {@link #next}.  The behavior of an iterator
     * is unspecified if the underlying collection is modified while the
     * iteration is in progress in any way other than by calling this
     * method.
     *
     * @throws UnsupportedOperationException if the {@code remove}
     *         operation is not supported by this iterator
     *
     * @throws IllegalStateException if the {@code next} method has not
     *         yet been called, or the {@code remove} method has already
     *         been called after the last call to the {@code next}
     *         method
     */
    void remove();
}

The key operations are:


Example: Using an Iterator

 LinkedList<Book> books = new LinkedList<Book>();
 books.add(cs330Text);
 books.add(cs361Text);
 books.add(cs252Text);
   ⋮
 boolean found = false;
 Iterator<Book> it = books.iterator();
 while (it.hasNext() && !found)
 {
   Book b = it.next();
   found = b.equals(cs252Text);
 }
 if (found)
 {
   it.remove();
 }


Not as Strange as it Looks

Compare that to the C++ equivalent:

 list<Book> books;
 books.push_back(cs330Text);
 books.push_back(cs361Text);
 books.push_back(cs252Text);
   ⋮
 bool found = false;
 list<Book>::iterator it = books.begin();
 while (it != books.end() && !found)
 {
   found = (*it == cs252Text);
   ++it;
 }
 if (found)
 {
   books.erase (it);
 }


Iterators: java and C++

Some rough correspondences:

Java C++
Iterator<E> container<E>::iterator
container.iterator() container.begin()
it.hasNext() it != container.end()
x = it.next(); x = *it; it++;
it.remove(); container.erase(it);

Limitations of the Java Iterator

In C++ we might write:

list<Book>::iterator find (list<Book>& books, 
                           string title)
{
  for (list<Book>::iterator it = books.begin();
         it != books.end(); ++it)
    {
     if (it->getTitle() == title)
        return it;
    }
  return books.end();
}

Once we have used the iterator (to check the title), it no longer points at the same location, so we can’t then return it as the position of the desired book.


Limitations of the Java Iterator (cont.)

Similarly, there’s no easy equivalent in Java to the C++ practice of using iterators as starting and ending positions of an operation:

template <typename Iterator>
Iterator copy (Iterator start, Iterator stop, 
               Iterator dest)
{
   while (start != stop)
     {
       *dest = *start;
       ++start; ++dest;
     }
}


Iterators and the “for each” Loop

Java Iterators enable the simple “for each” loop syntax.

For any container type C that has a function iterator() that returns a value of type Iterator<T>, we can rewrite

 C c = <: ... :>
   ⋮
 for (Iterator<T> it = c.iterator();
         it.hasNext(); ) {
   T t = it.next();
     ⋮
 }

by

 C c = <: ... :>
   ⋮
 for (T t: c) {
     ⋮
 }

2.2 Interface Implementation is Subtyping, not Inheritance


So What is it, Then?

A class that implements an interface can be used wherever that interface is “expected”. That’s pretty much our definition of subtyping.

3 Comparing Multiple Inheritance and Interfaces

Multiple inheritance and interfaces often wind up as ways of solving the same problems

For example, suppose we wanted to provide a class that collected a number of useful sorting algorithms.

How do we tell potential users of our sorting routines how to provide comparison functions that we can use?

3.1 An Inheritance-Based Solution

One solution is to define the “comparable” protocol as a class.

Java:

public class Comparable {
  public boolean comesBefore (Object o) { ...???... }
}

C++:

class Comparable {
public:
    virtual bool comesBefore (const Comparable& o) const;
}

A problem with this is that, eventually, the compiler will want us to supply function bodies for these. But we really can’t do a comparison for arbitrary objects, so we’ll need to do something simple like:

Java:

public class Comparable {
  public boolean comesBefore (Object o) { return false; }
}

C++:

class Comparable {
public:
    virtual bool comesBefore (const Comparable& o) const { return false; }
}

Then we can write our sorting functions to work on objects of this Comparable class (which we really never expect to ever see) or of any subtype of Comparable:

3.1.1 Calling on a Comparable Class

class Sorting {

   public static void
     insertionSort (Comparable[] array)
   {
      for (int i = 1; i < array.length; ++i) {
        Comparable temp = array[i];
        int p = i;
        while ((i > 0)
          && temp.comesBefore(array[p-1])) {
           array[p] = array[p-1];
           p--;
        }
        array[p] = temp;
     }
   }
     ⋮
 }

3.1.2 Extending Comparable

Here’s an example of how we might declare a class that overrides comesBefore to provide a “sensible” implementation we can use for sorting.

Java

 class Student extends Comparable
 {
   String name;
   String id;
   double gpa;
   String school;

   boolean comesBefore(Object o)
   {
     return gpa > ((Student)o).gpa;
   }

 }

C++

 class Student: public Comparable
 {
   String name;
   String id;
   double gpa;
   String school;

   virtual bool comesBefore(const Comparable& o) const
   {
     return gpa > ((Student)o).gpa;
   }

 }

In this case, we can sort students by grade point average.


A Closer Look

The downcast in our application

 class Student extends Comparable
 {
   String name;
   String id;
   double gpa;
   String school;

   boolean comesBefore(Object o)
   {
     return gpa > ((Student)o).gpa;
   }

 }

is ugly (but required) to match the parameter type of comesBefore.

abstract class Comparable {
  public abstract boolean comesBefore (Object o);
}

We needed a downcast in the C++ version as well.

3.1.3 Making Comparable Generic

We can get rid of the downcasts by making Comparable a C++ template or a generic in Java, so that it knows what kind of objects it is actually comparing:

Java:

public class Comparable<T extends Object> {
  public boolean comesBefore (T t) { return false; }
}

C++:

template <typename T>
class Comparable {
public:
    virtual bool comesBefore (const T& o) const { return false; }
}

and then letting the Student class pass that info along:

Java

 class Student extends Comparable<Student>
 {
   String name;
   String id;
   double gpa;
   String school;
 
   boolean comesBefore(Student s)
   {
     return gpa > s.gpa;
   }
 
 }

C++

 class Student: public Comparable<Student>
 {
   String name;
   String id;
   double gpa;
   String school;

   virtual bool comesBefore(const Student& s) const
   {
     return gpa > s.gpa;
   }

 }

3.1.4 A Lurking Problem

What if Student is already inheriting from another class?

 class Person
 {
   String name;
   String id;
 }
 
 class Student extends Person
 {
   double gpa;
   String school;
 }


Why is This a Problem?

That would be no problem in C++, which permits multiple inheritance:

class Student: public Person, Comparable {

But Java only allows a class to have a single superclass, so we can’t add extends Comparable

The solution is to make Comparable an interface instead of a class

3.2 An Interface-based Solution

Java programmers are more likely to approach this problem using an interface:

 package java.lang;
 
 public interface Comparable<T extends Object> {
   /**
       Compares this object with the specified object for order.
       Returns a negative integer, zero, or a positive integer
       as this object is less than, equal to, or greater than
       the specified object.
   */
   int compareTo (T other);
 }

3.2.1 Calling on a Comparable Interface

We use interfaces in our sorting routine just like we would use any kind of data type:

  public static void
     insertionSort (Comparable[] array)
   {
     for (int i = 1; i < array.length; ++i) {
        Comparable temp = array[i];
        int p = i;
        while ((p > 0)
          && temp.compareTo(array[p-1]) < 0) {
           array[p] = array[p-1];
           p--;
        }
        array[p] = temp;
     }
   }

3.2.2 Implementing the Comparable Interface

But now our student class does not have to inherit

comparableStudents.java
 class Student extends Person
               implements Comparable<Student>
 {
   double gpa;
   String school;
 
   int compareTo(Object o)
   {
     Student s = (Student)o;
     if (gpa < s.gpa)
       return -1;
     else if (gpa == s.gpa)
       return 0;
     else
       return 1;
   }
}

3.3 This was Just an Example


Extending Interfaces

Interfaces can extend (inherit from) other interfaces.

ListIterator.java
/*
 * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.util;

/**
 * An iterator for lists that allows the programmer
 * to traverse the list in either direction, modify
 * the list during iteration, and obtain the iterator's
 * current position in the list. A {@code ListIterator}
 * has no current element; its <I>cursor position</I> always
 * lies between the element that would be returned by a call
 * to {@code previous()} and the element that would be
 * returned by a call to {@code next()}.
 * An iterator for a list of length {@code n} has {@code n+1} possible
 * cursor positions, as illustrated by the carets ({@code ^}) below:
 * <PRE>
 *                      Element(0)   Element(1)   Element(2)   ... Element(n-1)
 * cursor positions:  ^            ^            ^            ^                  ^
 * </PRE>
 * Note that the {@link #remove} and {@link #set(Object)} methods are
 * <i>not</i> defined in terms of the cursor position;  they are defined to
 * operate on the last element returned by a call to {@link #next} or
 * {@link #previous()}.
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @author  Josh Bloch
 * @see Collection
 * @see List
 * @see Iterator
 * @see Enumeration
 * @see List#listIterator()
 * @since   1.2
 */
public interface ListIterator<E> extends Iterator<E> {
    // Query Operations

    /**
     * Returns {@code true} if this list iterator has more elements when
     * traversing the list in the forward direction. (In other words,
     * returns {@code true} if {@link #next} would return an element rather
     * than throwing an exception.)
     *
     * @return {@code true} if the list iterator has more elements when
     *         traversing the list in the forward direction
     */
    boolean hasNext();

    /**
     * Returns the next element in the list and advances the cursor position.
     * This method may be called repeatedly to iterate through the list,
     * or intermixed with calls to {@link #previous} to go back and forth.
     * (Note that alternating calls to {@code next} and {@code previous}
     * will return the same element repeatedly.)
     *
     * @return the next element in the list
     * @throws NoSuchElementException if the iteration has no next element
     */
    E next();

    /**
     * Returns {@code true} if this list iterator has more elements when
     * traversing the list in the reverse direction.  (In other words,
     * returns {@code true} if {@link #previous} would return an element
     * rather than throwing an exception.)
     *
     * @return {@code true} if the list iterator has more elements when
     *         traversing the list in the reverse direction
     */
    boolean hasPrevious();

    /**
     * Returns the previous element in the list and moves the cursor
     * position backwards.  This method may be called repeatedly to
     * iterate through the list backwards, or intermixed with calls to
     * {@link #next} to go back and forth.  (Note that alternating calls
     * to {@code next} and {@code previous} will return the same
     * element repeatedly.)
     *
     * @return the previous element in the list
     * @throws NoSuchElementException if the iteration has no previous
     *         element
     */
    E previous();

    /**
     * Returns the index of the element that would be returned by a
     * subsequent call to {@link #next}. (Returns list size if the list
     * iterator is at the end of the list.)
     *
     * @return the index of the element that would be returned by a
     *         subsequent call to {@code next}, or list size if the list
     *         iterator is at the end of the list
     */
    int nextIndex();

    /**
     * Returns the index of the element that would be returned by a
     * subsequent call to {@link #previous}. (Returns -1 if the list
     * iterator is at the beginning of the list.)
     *
     * @return the index of the element that would be returned by a
     *         subsequent call to {@code previous}, or -1 if the list
     *         iterator is at the beginning of the list
     */
    int previousIndex();


    // Modification Operations

    /**
     * Removes from the list the last element that was returned by {@link
     * #next} or {@link #previous} (optional operation).  This call can
     * only be made once per call to {@code next} or {@code previous}.
     * It can be made only if {@link #add} has not been
     * called after the last call to {@code next} or {@code previous}.
     *
     * @throws UnsupportedOperationException if the {@code remove}
     *         operation is not supported by this list iterator
     * @throws IllegalStateException if neither {@code next} nor
     *         {@code previous} have been called, or {@code remove} or
     *         {@code add} have been called after the last call to
     *         {@code next} or {@code previous}
     */
    void remove();

    /**
     * Replaces the last element returned by {@link #next} or
     * {@link #previous} with the specified element (optional operation).
     * This call can be made only if neither {@link #remove} nor {@link
     * #add} have been called after the last call to {@code next} or
     * {@code previous}.
     *
     * @param e the element with which to replace the last element returned by
     *          {@code next} or {@code previous}
     * @throws UnsupportedOperationException if the {@code set} operation
     *         is not supported by this list iterator
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this list
     * @throws IllegalArgumentException if some aspect of the specified
     *         element prevents it from being added to this list
     * @throws IllegalStateException if neither {@code next} nor
     *         {@code previous} have been called, or {@code remove} or
     *         {@code add} have been called after the last call to
     *         {@code next} or {@code previous}
     */
    void set(E e);

    /**
     * Inserts the specified element into the list (optional operation).
     * The element is inserted immediately before the element that
     * would be returned by {@link #next}, if any, and after the element
     * that would be returned by {@link #previous}, if any.  (If the
     * list contains no elements, the new element becomes the sole element
     * on the list.)  The new element is inserted before the implicit
     * cursor: a subsequent call to {@code next} would be unaffected, and a
     * subsequent call to {@code previous} would return the new element.
     * (This call increases by one the value that would be returned by a
     * call to {@code nextIndex} or {@code previousIndex}.)
     *
     * @param e the element to insert
     * @throws UnsupportedOperationException if the {@code add} method is
     *         not supported by this list iterator
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this list
     * @throws IllegalArgumentException if some aspect of this element
     *         prevents it from being added to this list
     */
    void add(E e);
}
addViaIterator.java
 LinkedList<Book> books = new LinkedList<Book>();
 books.add(cs330Text);
 books.add(cs361Text);
 books.add(cs252Text);
   <:\smvdots:>
 boolean found = false;
 ListIterator<Book> it = books.listIterator();
 while (it.hasNext() && !found)
 {
   Book b = (Book)it.next();
   found = (b.equals(cs252Text);
 }
 if (found)
 {
   it.remove();
 }
 it.add(cs252textVersion2);

4 Abstract Classes

Earlier, we considered the classes

Java:

public class Comparable {
  public boolean comesBefore (Object o) { return false; }
}

C++:

class Comparable {
public:
    virtual bool comesBefore (const Comparable& o) const { return false; }
}

Now, we really don’t expect to ever see someone declare a variable of type Comparable. We simply defined these classes to set up the idea of comparable objects so that we could use inheritance to express specialized approaches to that idea.

Providing these fake function bodies was just a way to get the code to compile. We don’t actually expect our code to ever execute either of the two function bodies above. We expect it to always be executing some body that overrides these in a subclass. In fact, if we ever did execute these, that would be a sign that something had gone wrong rather badly.

Both programming languages allow us to express the idea that “we don’t want to provide function bodies – we just want to establish the existence of this function” via abstract classes, also known as pure virtual classes.

In C++ we use “= 0;” at the end of a member function declaration to indicate that don’t intend to ever supply a body for that function in that class.

class Comparable {
public:
    virtual bool comesBefore (const Comparable& o) const =0;
}

A function so marked is called an abstract member function. A class with one or more abstract member functions is an abstract class.

A class that inherits from an abstract class is also abstract unless it overrides all inherited abstract functions and provides function bodies for all of them.

Abstract classes work much the same in Java as in C++. The only differences are

Java

public abstractclass Comparable {
  public abstract boolean comesBefore (Object o);
}

4.1 Limitations of Abstract Classes

Abstract classes carry some limitations, designed to make sure we use them in a safe manner.

We’ll look more at abstract classes in the upcoming lesson on OO idioms.