aspose file tools*
The moose likes Java in General and the fly likes multiple inheritance & Java Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login
JavaRanch » Java Forums » Java » Java in General
Bookmark "multiple inheritance & Java" Watch "multiple inheritance & Java" New topic
Author

multiple inheritance & Java

manish ahuja
Ranch Hand

Joined: Oct 23, 2003
Posts: 312
Hi All

What are the downsides of multiple inheritance that the creators of Java decided not to incoprorate it.

The only one I saw while going through this forum was when say there 2 parent classes & both have same method names & the same signature so the inherting child class will run into problems

This is the only . If there are a few more can someone list them

Bye
Manish
Guy Allard
Ranch Hand

Joined: Nov 24, 2000
Posts: 776
Manish - I will not answer your question. Others perhaps will.

My comments:

-Look at the history of languages.
-In the early '80s MI was a raging contrversy during the design/development of C++
-Today Java does not allow MI
-Today C# does not allow MI

Mistakes IMO.

One of the people on the C++ design team said (roughly):

MI is like a parachute. You don't need it very often, but when you do, you are very glad to have it.


I don't remember who said that but he was correct I believe. I personally can live without MI. Sometimes not convenient, and in some (many?) cases does not allow reuse, but tolerable for me.

Java's main lack IMO is no operator overloading. How can I write a robust 'Complex' class to deal with complex numbers, and give the API to an engineer (an ad hoc part-time programmer) and hope to have him/her deal with it? I cannot, they will not tolerate it, and rightly so.

Well, an old programmer has grouched enough .........

Regards, Guy
Tim West
Ranch Hand

Joined: Mar 15, 2004
Posts: 539
One is performance - the compiler/interpreter has to work out which method to call in which situation. Dynamic dispatch in this circumstance can be very expensive.

Another is complexity - code that uses multiple inheritance is often very hard to understand. Also, there's a group of 'anti-patterns' that are based around using multiple inheritance rather than properly separating concerns with something like the GoF bridge pattern.

There are a few ways of dealing with clashes. One I guess is to not allow them at compile-time, like you say. A much nicer way is Eiffel's way: there's a clause in the class definition where you can rename one or both of the methods.

There are many other reasons I'm sure, but there's a start.


--Tim
manish ahuja
Ranch Hand

Joined: Oct 23, 2003
Posts: 312
Hi Tim & Guy

Thanks for your response.

I am a total newbie to the world of computer programming & now learning Java/j2ee.

Hey Tim could you ellaborate the Eiffel rule is possible with an illustration.

Thanks
Ilja Preuss
author
Sheriff

Joined: Jul 11, 2001
Posts: 14112
Originally posted by Tim West:
One is performance - the compiler/interpreter has to work out which method to call in which situation. Dynamic dispatch in this circumstance can be very expensive.


So you are saying that dynamic dispatch with MI is much more costly than without MI? Could you please elaborate/provide pointers?

My personal impression is that Sun saw three different ways to go:

a) implement MI in a "quick and dirty" way,
b) invest a significant amount of resources into implementing MI in a nice and consistent way, or
c) not implement MI at all

I am sure we all agree that a) would have been bad. I guess that they didn't see enough value in MI to do b), so they decided for c). Does sound reasonable to me, even if a can understand people wh0 wish they'd have decided for b)...


The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
Tim West
Ranch Hand

Joined: Mar 15, 2004
Posts: 539
Originally posted by Ilja Preuss:

So you are saying that dynamic dispatch with MI is much more costly than without MI? Could you please elaborate/provide pointers?


That's was my understanding of the situation, yeah. I don't know enough about compiler design to be able to back this statement up, though, so I could be flat-out wrong.

I guess it depends how you handle MI. My impression is you could either make it relatively complex (for the programmer) but fast (speed of resulting binary) as in C++, or less complex but slower as in Eiffel.

In Eiffel, overriding is simple syntactically but there are significant overheads. I guess smart compilers remove much of this at compile time, still, two main factors can slow down the calling of an overridden method*:

  • An inherited method can be renamed.
  • An inherited method can have extra preconditions and postconditions added to it. When called, all pre/postconditions (those of the child class and all its parents) are checked.


  • In any case, I will try to read up on this over the weekend to see if I'm right. Thanks for taking me up on this one though, you gotta be able to back-up things you say eh? =)


    --Tim


    *I say "calling of" because it's not only dynamic dispatch (that is, run-time selection of which method to call) that's slow. Rather, it's the whole overhead of calling a method.
    Ilja Preuss
    author
    Sheriff

    Joined: Jul 11, 2001
    Posts: 14112
    Originally posted by Tim West:
    An inherited method can be renamed.


    I think that would at least be handled (that is, optimized) by the Hotspot engine, so that there would be no significant overhead.


    An inherited method can have extra preconditions and postconditions added to it. When called, all pre/postconditions (those of the child class and all its parents) are checked.


    That's probably true independent of MI, isn't it?
    sever oon
    Ranch Hand

    Joined: Feb 08, 2004
    Posts: 268
    MI, strictly speaking, is unnecessary. Also, it introduces a potential pitfall into the language that can be resolved two ways, each being equally inelegant--that is, from a design standpoint, there is no good reason to choose one way over the other, so it's basically an arbitrary decision of the language.

    I refer to the so-called dreaded "diamond problem". Consider the following:



    What does running Sub12's main method do? Will it print "Base" or "Sub1"...what implementation of the foo() method does Sub12 inherit: Sub1's, which overrides Base's implementation, or Sub2's, which simply inherits it and passes it down? (If you draw out the inheritance hierarchy in UML, you'll see why it's called the diamond problem.)

    The fact is, OO experts have made weak arguments for a couple of decades why one approach is better than the other, mostly based on their personal preference, but the fact is that the choice between what Sub12 should do is equally well (badly?) justified either way. This is, of course, not a showstopper and a slam-dunk argument against MI, but it is an indicator of things to come--if the language itself can't even behave in an expected, deterministic fashion when presented with a very simple case of object inheritance hierarchies converging, how can this feature stand up in large, complex software projects?

    There are better arguments to be made against MI, all of them more involved. To understand what I think is the most compelling one of these is too long to lay out here in full form, and requires you to really understand the difference between an abstract class and an interface (even a pure abstract class, that happens to have no method implementations). That difference, briefly, is that an interface says more, design-wise, than a pure abstract class in terms of how the project is going to evolve. When an abstract class is extended, the developer is (unwittingly, perhaps) agreeing that any method implementations added to that abstract class ought to be inherited by the subclass, no matter how bizarre they may be, no matter how many versions later those implementations are added. This binds a subclass to an abstract class in a very tight and intimate way, placing all sorts of extra restrictions on the assumptions it can make about the contract of the abstract class.

    When a class implements an interface, on the other hand, it is assured that there will never be anything inherited from that interface except what it's getting right now. If the interface designer goes back and adds another method or changes an existing method signature, the compilation will break, unlike the abstract class if the developer adds a method implementation.

    So if the relationship between a subclass and an abstract class is so intimate and so tightly coupled, is it better to have classes implement interfaces or extend abstract classes? Obviously interfaces is, as a rule, a hallmark of better design. So why, then, would it be a good idea to enable classes to extend not just one abstract class (or worse, and more tightly coupled, a non-abstract class!), but any number of them?

    There are better alternatives. Several of design patterns address this very problem. Also, it is possible to achieve all the benefits of MI using a system of separating class APIs from class implementations and using aggregation, and this approach avoids the dreaded diamond problem.

    You might say, wait, how often would the diamond problem happen in Java if Java allowed MI? Just do a good design and don't let it happen! This is nearly impossible, because every class in Java is a subclass of java.lang.Object, all MI would result in this diamond, meaning that you cannot override any of Object's methods in classes that are to be used in an MI hierarchy without encoutering it. Some say the Java compiler could just recognize the conflict and force you to implement it either as:



    ...or...



    I think this just transfers a lot of code bloat--one of the main selling points of MI is that it removes code bloat, so by introducing it it's working at cross-purposes with one of stated goals of the language.

    Now consider a situation where it seems like a good idea to use MI, though, and question whether it cannot be done another, better way. Let's say you have classes Base1, Base2, and Base3 and you want to implement a classes Sub1, Sub2, and Sub3, each of which extends all of Base1, Base2, and Base3. In other words, if MI were an option, you could write:



    What are the advantages of inheritance that you're trying to exploit by using MI here? There are a few big ones as far as I can see. When a class Sub extends another class Base:

  • objects of type Sub can be polymorphically treated as objects of type Base.
  • changes in method bodies in Base are *automatically* inherited by Sub, without having to update code in Sub.
  • if ref instanceof Sub == true, then ref instanceof Base == true as well.
  • Sub can override methods in Base.


  • If a class Sub extends another class Base, then it meets all of the above criteria.

    Many people will probably reply to your question with the age-old answer, "Prefer aggregation to inheritance". It's true that you can draft a good design that will use aggregation in place of MI in most cases. However, in some cases, you might prefer MI because you want *all* of the above conditions to be true for Sub wrt Base1, Base2, and Base3. In those cases, simple aggregation will not get you that:



    In the above example... Can Sub be polymorphically treated as type Base? No. Are changes to foo() in Base1 automatically available to Sub without changing code in Sub? Yes. If some reference (ref instanceof Sub) == true, will (ref instanceof Base) == true? No. And finally, can Sub "override" methods of Base? Kind of--not actually "overriding" technically, but if I were to replace Sub's implementation of foo() with some other code, then that would approximate overriding Base's foo().

    Still, though, we're left sorely lacking on the first and third points, so in cases where those points are important aggregation alone isn't quite cutting it. Worse yet, what if I want to simulate MI in not just one subclass, but three: Sub1, Sub2, and Sub3? Do I have to write this aggregation code for each and every one of them? Isn't there some way we can provide an easy means for other developers to extend these three Base classes easily? Is there any way we get all of the advantages of direct MI in Java through design?

    I believe yes, but it requires separating Base1, Base2, and Base3's contracts (APIs) from their implementation classes. Once you do this, you can provide a single class that serves as a code container for all of the aggregation code. You can look at this as a sort of manual way to do MI for the Java compiler:



    Notice I've marked the AbstractSub class protected (at the class level). This won't compile (classes can't be protected in Java), but it makes the point that no one but subclasses ought to "know" about that class. This is because I don't ever want code in the system to deal with objects polymorphically as type AbstractSub--instead, all subclassees of AbstractSub can be polymorphically treated as type Sub, the interface type (AbstractSub's docs should spell this out explicitly). AbstractSub therefore is specified only for one purpose: to act as a code container for subclasses to inherit all of that messy aggregation code. This is why I provided the Sub interface above, even though it is not strictly necessary (it could be deleted and AbstractSub could have simply implemented Base1, Base2, and Base3 instead): it provides a way for other code to treat Sub1, Sub2, and Sub3 polymorphically without having to decide between Base1, Base2, and Base3. What if a client wants to create an array of subs and call foo(), bar(), and baz() on each element without a bunch of messy typecasting? Can't be done without the Sub interface--since that's there, though, one could simply write:



    Now let's go through the check list to see if we've hit all the points:

    Can Sub1, Sub2, and Sub3 all be treated polymorphically as type Base1, Base2, and Base3? Yes.

    Will changes to method bodies in any of the Base classes be automatically inherited by Sub1, Sub2, and Sub3? Yes.

    If (ref instanceof Sub) == true, is (ref instanceof Base1 && ref instanceof Base2 && ref instanceof Base3) == true? Yes.

    If Sub1, Sub2, or Sub3 provide foo(), bar(), or baz(), will these methods override the Base implementations? Yes.

    Because all of these conditions are satisfied, we can consider any class that extends AbstractSub as having multiply inherited from Base1, Base2, and Base3 in every way.

    One of these days, when I get time I'm going to see if there's a good way to implement this MI stuff using Java 1.5's new metadata facility, or maybe generics. Probably not very useful, but loads of fun.

    sev
    Tim West
    Ranch Hand

    Joined: Mar 15, 2004
    Posts: 539
    Ilja, AFAIK you're right on both counts. Still, I'm going to pig-headedly retain my belief that dynamic dispatch is (or can be, depending on how MI is implemented) slower with MI than without. I still can't back-up that statement though...I'll Google it later.

    Sev, comprehensive post! Just out of interest (referring to your initial syntax problem), if an Eiffel-like implementation of MI were used in Java we may find ourselves typing stuff like this:




    --Tim
    [ July 18, 2004: Message edited by: Tim West ]
    sever oon
    Ranch Hand

    Joined: Feb 08, 2004
    Posts: 268
    I actually prefer the "manual" way of implementing MI in Java, as I described above. I've used it a couple of times where MI was simply the best solution, but the funny thing is that in cases where MI is really best, the situation is always complicated enough such that there are other benefits to splitting off the API of the superclasses into separate interfaces.

    The first time I used it I thought, oh no, this is going to be a nightmare to document what I'm doing here. Then, as the system got more and more complex and I considered other parts of the design, it became very unimportant to even point out that MI was happening at all. It was enough to consider the relationships of the classes with each other on a one-by-one basis without having to grok the big picture as having anything at all to do with MI in the first place. It's almost as if a language-supported MI mechanism rolls up all of these object relationships that you tacitly agree to when you use it, but rarely do you want them all. In the rare cases where you actually do want each of those object relationships as expressed in my post above, it is natural to simply consider each of them independently.

    That the mess of collaborations above happens to satisfy the conditions of MI is more an academic realization than a practical one.

    sev
     
    Don't get me started about those stupid light bulbs.
     
    subject: multiple inheritance & Java