Multiple Inheritance, Interfaces, and Abstract Classes
Steven Zeil
Look at this set of classes.
It expresses a set of ideas familiar to most college students.
- Everyone at a University has a name, address, and ID number.
- Some of the people at a University are teachers. Some are students. These two groups of people may have different attributes and behaviors.
- For example, students have grade point averages (and the data attributes allowing the calculation of a GPA). Teachers have salaries and get paid.
- But, both teachers and students share the common attributes of all University people: names, addresses, and ID numbers.
- Graduate TAs, however, are simultaneously teachers and students. They take their own graduate course and have GPAs, but they also are paid for their work in a department.
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();
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.
-
An interface declares a related set of
- member function declarations
- constant values
-
Classes may be declared to implement an interface independently of where they are in the inheritance hierarchy.
2.1 Examples
2.1.1 AudioClip
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.
- Otherwise,
Object.clone()
will throw an exception.package java.lang; public interface Cloneable { public Object clone(); }
2.1.3 Iterators in Java
- A Java interface for looping through data structures
/*
* 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:
-
hasNext(): tests to see if there are more elements to be visited
-
next(): advances the iterator to the next position and returns the element that it had been pointing to.
-
Most containers provide one or more functions that return iterators.
- The most common name for that function is iterator()
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); |
- Notice that you can’t retrieve a value from the position denoted by a Java iterator without advancing the iterator.
Limitations of the Java Iterator
- One consequence of that is that Java iterators are
- good for looping, but
- not useful as positions where something was found when searching.
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();
}
- There’s no equivalent to that using Java iterators.
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
-
You cannot inherit variables from an interface.
-
You cannot inherit method implementations (function bodies) from an interface.
-
The interface hierarchy is independent of a the class hierarchy.
-
A Java class may implement many different interfaces, but can only inherit from one superclass.
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.
-
All sorting algorithms require the ability to compare objects.
-
But class
Object
has no comparison function exceptequals
. C++ classes, in general, don’t even have that.
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
- It simply announces its intention to implement the interface:
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;
}
}
- Of course, having made that intention clear, we have to follow up by providing the required function(s) of the interface.
-
By the way,
Comparable
is not something I have made up - it’s part of the Java API.
-
3.3 This was Just an Example
- In practice, we would not write out own sorting function, but would use
std::sort
in C++ orArrays.sort
in Java.-
and, to sort other Java data structures, the similar functions declared in
java.util.Collection
.
-
Extending Interfaces
Interfaces can extend (inherit from) other interfaces.
/*
* 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);
}
- The ListIterator extends Iterator by adding a function to add data at a position:
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
-
In Java, you must label both the class and its abstract functions. (In C++ you label only the functions.)
-
Instead of putting
=0
at the end of the function, you put the keywordabstract
in front of the function/class declaration.
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.
-
You cannot construct an object whose type is an abstract class.
-
So, in C++,
Comparable c;
would be illegal.
-
In Java,
... new Comparable()...;
would be illegal. That would also be illegal in C++.
-
-
In C++, you cannot declare function parameters of an abstract class type when passing parameters “by copy”.
void foo (Comparable c); // illegal
but you can pass pointers/references to the abstract class type.
void foo (Comparable& c); // OK void foo (Comparable* c); // also OK
- In Java,
void foo (Comparable c); // illegal
would be OK, because that
Comparable
parameter is “really” a reference.
- In Java,
We’ll look more at abstract classes in the upcoming lesson on OO idioms.