11.5 Multiple Inheritance

Java is a single-inheritance language, like Ada 95 and Smalltalk. Each class (except for Object) is based on exactly one other class; the derived class extends the class it is based on. An instance of the derived class may be used in any place where an instance of the base class is called for. The derived class has all of the behaviors of the base class, and it may add a few of its own.

There are several reasons for a class to have only one superclass. One is a matter of performance. There are implementation techniques for the JVM that execute method invocations very quickly if there is only one superclass. When multiple superclasses are permitted, it becomes difficult to arrange memory for maximum efficiency.

Another reason not to support multiple inheritance is that it adds a new layer of complexity to the language. If a class has several superclasses and two of the superclasses share a method with the same name and arguments, then which method is inherited by the class? Various language designs get around this problem in different ways, but no way is as easy as simply limiting the class to one superclass.

11.5.1 Interfaces for Multiple Inheritance

Although the JVM does not support multiple inheritance of code, it does support multiple inheritance of interfaces. Since interfaces have no implementations, they are not subject to the problems of multiple inheritance. Interfaces can be used to help implement multiple inheritance.

Suppose you want to implement this class hierarchy, which is expressed in a Java-like language that supports multiple inheritance:

 class Person {    String getName() {/* Implementation of getName() */ }    int getSalary() {/* Implementation of getSalary() */ } } class FootballPlayer extends Person {    void wearHelmet() {/* Wear a football helmet */ }    String getTeam() {/* Return the player's football team */ } } class BaseballPlayer extends Person {    void swingBat() {/* Swing a baseball bat */ }    String getTeam() {/* Return the player's baseball team */ } } class DeionSanders extends BaseballPlayer, FootballPlayer { } 

We start by declaring an interface for each class. The definition of DeionSanders inherits multiple interfaces:

 .interface Person .method getName()Ljava/lang/String; .end method .method abstract getSalary()I .end method .interface FootballPlayer .implements Person .method abstract wearHelmet()V .end method .method abstract getTeam()Ljava/lang/String; .end method .end class .interface BaseballPlayer .implements Person .method abstract swingBat()V .end method .method abstract getTeam()Ljava/lang/String; .end method .end class .interface DeionSanders .implements FootballPlayer .implements BaseballPlayer .end class 

To be useful, these interfaces must be implemented by classes.

11.5.2 Implementing the Interfaces

In order to use these interfaces, classes that implement them must be provided. We will call these classes Person$Impl, FootballPlayer$Impl, BaseballPlayer$Impl, and DeionSanders$Impl. Each class implements the corresponding interface and provides implementations for the methods. The implementations are defined according to the semantics given in the original program.

The class Person$Impl involves no inheritance, so we'll look at it first. Its methods are implemented according to the definitions provided in the original source code:

 .class Person$Impl .implements Person .method public getName()L/java/lang/String;    ;; Compute name and return it .end method .method public getSalary()I    ;; Compute salary and return it .end method .end class 

Since FootballPlayer$Impl and BaseballPlayer$Impl have to implement only one interface, they can inherit the definitions of getName and getSalary from Person$Impl. For those methods which are defined just for the class (getTeam and wearHelmet from FootballPlayer, getTeam and swingBat from BaseballPlayer), these methods are implemented in the class according to the definition in the original source:

 ; Implement the FootballPlayer interface .class FootballPlayer$Impl .super Person$Impl .implements FootballPlayer .method public getTeam()Ljava/lang/String;    ;; Compute and return the player's football team .end method .method public wearHelmet()V    ;; Wear a football helmet .end method .end class ; Implement the BaseballPlayer interface .class BaseballPlayer$Impl .super Person$Impl .implements BaseballPlayer .method public getTeam()Ljava/lang/String;    ;; Compute and return the player's baseball team .end method .method public swingBat()V    ;; Swing a baseball bat .end method .end class 

Implementing DeionSanders$Impl is a bit trickier. It defines no methods of its own, but it needs to have both wearHelmet from FootballPlayer$Impl and swingBat from BaseballPlayer$Impl. Unfortunately, DeionSanders$Impl can't inherit from both FootballPlayer$Impl and BaseballPlayer$Impl, because the JVM forbids multiple inheritance of implementation. Fortunately, there are several ways to solve this problem.

One way is to use one class or the other as the superclass. It will inherit the methods from that class, but not from the other. Methods that are not inherited must be rewritten, perhaps by copying the code from the implementation. Suppose we choose FootballPlayer$Impl as the base class of DeionSanders$Impl:

 .class DeionSanders$Impl .super FootballPlayer$Impl ; All the FootballPlayer method implementations are inherited ; The BaseballPlayer-specific methods must be written explicitly .method swingBat()V    ;; Copy the code from BaseballPlayer$Impl.swingBat .end method .end class 

Using this definition of DeionSanders$Impl, calling getTeam on a DeionSanders object returns the team as a football player (the Dallas Cowboys). Depending on the semantics of the original language, this may or may not be the correct answer. In the real world, there are two different answers to the question "What team does Deion Sanders play for?" (As of the time of this writing, the answers are the Dallas Cowboys and the San Francisco Giants.)

Consider the equivalent classes in C++, which supports multiple inheritance:

 class Person {    public:        virtual char* getName() {/* Implement name */ }        virtual int getSalary() {/* Implement getSalary */ } }; class FootballPlayer : public Person {    public:        virtual char* getTeam() {/* Implement getTeam */ }        virtual void wearHelmet() {/* Implement wearHelmet */ } }; class BaseballPlayer : public Person {    public:        virtual char* getTeam() {/* Implement getTeam */ }        virtual void swingBat() {/* Implement swingBat */ } }; class DeionSanders : public FootballPlayer, BaseballPlayer { }; 

Any call to team through a pointer to a DeionSanders object would be ambiguous. The C++ compiler rejects programs with ambiguous calls. The caller of the getTeam method must explicitly disambiguate the call by including the name of the class containing the method to invoke:

 DeionSanders *ds = new DeionSanders(); ds->getTeam();                      // This is an error, since                                     // it is ambiguous ds->FootballPlayer::getTeam();      // Returns the Cowboys ds->BaseballPlayer::getTeam();      // Returns the Giants 

In Eiffel, the DeionSanders class itself would be illegal because of the ambiguity. The ambiguity can be fixed through renaming:

 class DeionSanders    inherit    FootballPlayer rename getTeam as getFootballTeam end    BaseballPlayer rename getTeam as getBaseballTeam end end 

In the Common Lisp Object System, this kind of ambiguity is legal. A call to the getTeam method of a DeionSanders object would return the football team, since by default the object uses the first method matching the name. By using the Meta Object Protocol, the default way of searching for method implementations can be changed to use whatever search order is desired.

Another way to implement DeionSanders$Impl is to use neither class as the base class. Instead, each object possesses private instances of both FootballPlayer$Impl and BaseballPlayer$Impl, called super1 and super2. Whenever a method is called, the class defers the implementation of that method by calling the equivalent method on either the super1 object or the super2 object. The class java/lang/Object is used as the base class.

 .class DeionSanders$Impl .super java/lang/Object .field private super1 LFootballPlayer$Impl; .field private super2 LBaseballPlayer$Impl; .method wearHelmet ()V    ; Defer wearHelmet to the FootballPlayer object    aload_0    getfield DeionSanders$Impl/super1 LFootballPlayer$Impl;    invokevirtual FootballPlayer$Impl/wearHelmet ()V    return .end method .method swingBat ()V    ; Defer swingBat to the BaseballPlayer object    aload_0    getfield DeionSanders$Impl/super2 LBaseballPlayer$Impl;    invokevirtual BaseballPlayer$Impl/swingBat ()V    return .end method .method getTeam()Ljava/lang/String;    ;; Do you defer this to super1 or super2? See below .end method .method getName()Ljava/lang/String;    ;; Do you defer this to super1 or super2? See below .end method .method getSalary()I    ;; Do you defer this to super1 or super2? See below .end method .method <init> ()V    ;; Initialize the super1 and super2 classes to instances of    ;; BaseballPlayer$Impl and FootballPlayer$Impl, respectively .end method .end class 

Although this technique solves the asymmetry between FootballPlayer$Impl and BaseballPlayer$Impl, it does run into a problem with getTeam, getName, and getSalary. Both methods must be implemented, since the FootballPlayer and BaseballPlayer interfaces both require it. It is not obvious which object these methods should be deferred to. In the real world, the person Deion Sanders has only a single name but two salaries, one as a baseball player and one as a football player. He also plays for two different teams.

Again, it is up to the semantics of the underlying language to resolve how these difficulties should be resolved. Different languages resolve these ambiguities differently, and programmers using each language find that the semantics of the language sometimes do and sometimes don't capture their intention. This is one of the reasons multiple inheritance is not included in Java.

11.5.3 Fields

JVM interfaces cannot have fields; they can have only methods. This means that it is not possible to specify fields the same way we specified methods in the interfaces.

One solution is to replace fields with accessor methods. Accessor methods are methods that get and set the value of the field. For example, suppose we add a height field to the Person class:

 float height;             // Height in meters 

It is not possible to add a height field to the Person interface, since that is not valid in the JVM specification. However, we can add getHeight and setHeight methods to the Person interface. This is similar to the JavaBeans design pattern for properties. By convention, method names beginning with get and set get and set some property of an object.

 .method abstract getHeight()F .end method .method abstract setHeight(F)V .end method 

These methods must be implemented in the Person$Impl class. This can be done with a private field height. The method getHeight returns the value of height, and setHeight sets the value of the height field.

 .field private height F        ; Stores the actual height .method getHeight()F           ; Get the height    aload_0    getfield Person$Impl/height F    freturn .end method .method setHeight(F)V          ; Set the height    aload_0    fload_1    putfield Person$Impl/height F    return .end method 

Any access to the height field of a Person object must be translated into getHeight and setHeight calls.



Programming for the Java Virtual Machine
Programming for the Javaв„ў Virtual Machine
ISBN: 0201309726
EAN: 2147483647
Year: 1998
Pages: 158
Authors: Joshua Engel

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net