Andreas Solymosi

Multiple Inheritance from Abstract Classes in Java

Abstract: a practical situation where multiple inheritance is inherent – it cannot be solved in Java without changing a superclass.

Y

The developers of Java renounced multiple inheritance – their arguments have been discussed often enough.

In many situations, multiple inheritance can be simulated by instantiation (delegation). Let’s assume we would like to inherit from classes Super1 and Super2:

class Sub extends Super1, Super2 {} // not Java!

Then we have to decide either for

class Sub implements InterfaceOfSuper2 extends Super1 {

   InterfaceOfSuper2 super2 = new Super2();

   … }

or for

class Sub implements InterfaceOfSuper1 extends Super2 {

   InterfaceOfSuper1 super1 = new Super1();

   … }

The not inherited methods (and other exports) must be passed through, cumbersome:

class Sub implements InterfaceOfSuper2 extends Super1 {

   InterfaceOfSuper2 super2 = new Super2();

   void methodFromSuper2() {

     super2.methodFromSuper2(); }

   SomeType functionFromSuper2() {

     return super2.functionFromSuper2(); }

   … }

Typically we decide for the more convenient superclass: the one exporting more methods, in order to work less with passing through.

However, if superclasses are abstract, they can’t be instantiated. Then there is no way to simulate multiple inheritance without changing the superclasses.

Let’s assume several implementations of the interface Queue with the usual methods add, remove, etc.:

class ArrayQ<E> implements Queue<E> { … }

class ListQ<E> implements Queue<E> { … }

An extension of the interface Queue with additional methods (like copy and equals) can be implemented through inheritance from ArrayQ and ListQ:

class ExtArrayQ<E> extends ArrayQ<E> implements ExtQ<E> { … }

class ExtListQ<E> extends ListQ<E> implements ExtQ<E> { … }

Other extensions of the interface Queue can have other additional methods; for example a Persistent Queue has methods like save and load:

interface PersQ<E> extends Queue<E> {

   void save(String filename);

   void load(String filename); }

These methods can be implemented in different ways like a binary file (through an ObjectIn/OutputStream) or an XML file:

class PersArrayObjectQ<E> extends ArrayQ<E>

     implements PersQ<E>, Serializable {

   … } // implementation with ObjectIn/OutputStream

class PersArrayXMLQ<E> extends ArrayQ<E> implements PersQ<E> {

   … } // implementation with XML

// similarly for ListQ

 

All these implementations have a common algorithm: loop through the queue and manipulate the elements. The idea of functional abstraction is to bin out the algorithm to an iterator:

class ItArrayQ<E> extends ArrayQ<E> implements Iterable<E> {

   public Iterator<E> iterator() { … } } // iterator for array

class ItListQ<E> extends ListQ<E> implements Iterable<E> {

   public Iterator<E> iterator() { … } } // iterator for list

and then the extension (copy and equals) can be easily implemented through inheritance from ItArrayQ and ItListQ:

class ExtItArrayQ<E> extends ItArrayQ<E> implements ExtQ<E> {

   … } // copy and equals implemented with iterator

class ExtItListQ<E> extends ItListQ<E> implements ExtQ<E> {

   … } // copy and equals implemented with iterator

Of course, the two class bodies above are the same: functional abstraction works. To complete it, we create an abstract class containing the abstract (technology independent) algorithm:

abstract class ExtItQ<E> implements ExtQ<E>, Iterable<E> {

   public boolean equals(ExtQ<E> q) {

     Iterator<E> thisOne = this.iterator(),

        thatOne = ((ExtItQ<E>)q).iterator();

     while (thisOne.hasNext() && thatOne.hasNext())

        if (thisOne.next() != thatOne.next())

           return false;

     return !thisOne.hasNext() && !thatOne.hasNext(); }

   public void copy(ExtQ<E> q) {

        this.clear();

        Iterator<E> thatOne = ((ExtItQ<E>)q).iterator();

        while (thatOne.hasNext())

           this.add(thatOne.next()); } }

This class is abstract because it doesn’t implement Iterable.iterator – this would be a technology dependent method.

To mix the implementations of iterator and equals/copy, multiple inheritance would be needed:

class ExtItArrayQ<E> extends ExtItQ<E>, ItArrayQ<E> {} // not Java!

Since Java doesn’t support it, we have to decide, which superclass to instantiate. ItArrayQ would be nice to keep as superclass since it exports more methods, but ExtItQ is abstract, it cannot be instantiated. So perforce we have to work cumbersome:

class ExtItArrayQ1<E> extends ExtItQ<E> { // extends ItArrayQ<E>

   private ItArrayQ<E> iaq;

   public ExtItArrayQ1(int size) { iaq = new ItArrayQ<E>(size); }

   public void add(E o) { iaq.add(o); }

   public void remove() { iaq.remove(); }

     … // equals and copy inherited

   public Iterator<E> iterator() { return iaq.iterator(); } }

class ExtItListQ<E> extends ExtItQ<E> … // similar

However, this is luck: one of the two superclasses is not abstract. If both are abstract, none of them can be instantiated. In this case there is no way in Java without changing them.

Let’s assume, we performed functional abstraction for Extended Queue and Persistent Queue like above. Now we need a Universal Queue capable of everything:

interface UnivQ<E> extends ExtQ<E>, PersQ<E>

It would be easy to implement it with multiple inheritance:

class UnivArrayQ<E> extends ItArrayQ<E>, ExtItQ<E>, PersItQ<E> // not Java!

class UnivListQ<E> extends ItListQ<E>, ExtItQ<E>, PersItQ<E> // not Java!

Here we have unfortunately two abstract superclasses: ExtItQ and PersItQ – we can instantiate only one (ItArrayQ) of the three.

This problem cannot be solved in Java without changing a superclass. We have to make them to inherit from each other:

abstract class ExtItQ1<E> extends PersItQ<E> // changed!

   implements ExtQ<E>, Iterable<E> { … } // like above

class UnivArrayQ<E> extends ExtItQ1<E> implements UnivQ<E> {

   private ItArrayQ<E> iaq; … } // like above

class UnivListQ<E> extends ExtItQ1<E> implements UnivQ<E> {

   private ItListQ<E> ilq; … } // like above

A class diagram presents the dependency of types in this example:

In this class diagram we presented interfaces and interface extensions red, implementations of an interface green; black arrows represent inheritance. Names of abstract classes and interfaces are italic (as usual).

The top of the hierarchy is Queue, extended by ExtQ and PersQ, extended by UnivQ, implemented by UnivArrayQ and UnivListQ (left bottom). ExtQ and PersQ are partially implemented by PersItQ, being (unfortunately) extended by ExtItQ1; these two implement Iterable. The abstract classes are the technology independent implementations of the interfaces ExtQ and PersQ. Technology dependency comes in with the classes ArrayQ and ListQ (implementing Queue), extended by ItArrayQ and ItListQ (implementing Iterable). Now it would be desirable UnivArrayQ and UnivListQ mixing ItArrayQ (resp. ItListQ) with ExtItQ1 and PersItQ. Lack of multiple inheritance they have to instantiate (or delegate to) ItArrayQ (resp. ItListQ: diamond arrow). Because ExtItQ1 (unfortunately perforce) extends PersItQ, they inherit from both.

The arrow leading from ExtItQ1 to PersItQ is very undesirable and perforce; it destroys independence of implementations and is retroactively necessary only for substitution of multiple inheritance among abstract classes.


Version: 23. Dezember 2010

© Prof. Solymosi, 2010, Beuth-Hochschule für Technik Berlin, Faculty for Computer Science and Media

solymosibht-berlin.de