Friday, July 28, 2006

Why is super.super illegal?

Well, from time to time I have the need to call super.super. I wonder why this is illegal. Doing some research on the Internet, the best answer I can come up with is, that it breaks encapsulation of the super.super class. Hmm, probably true in a clean world, but sometimes you really want to call back back the original functionality:

class A {
protected void foo(){..}
}
class B extends A {
protected void foo(){
super.foo();
doSomethingStupid();
}
}
class C extends B {
protected void foo(){
super.super.foo(); // it is illegal to omit the stupid stuff of class B
doSomethingUseful();
}
}

Yes, B should not do stupid things. But why can't C undo what B does? In real software systems, that's exactly something that is somtimes needed. The answer that B should provide access to A.foo() if B thinks C would need it does not really satisfy me...

Come on, if java would really be concerned about encapsulation, all methods would default to final unless otherwise specified. Does everybody think about the fact that any non-final public or protected method can be overridden? No! We live with this accidental breakage of encapsulation and why is the small breakage of encapsulation that super.super does forbidden? Hey, a subclass can just override any non-final method without calling super at all, right? And C can call any method on B or A, but not super.super.foo()....

But maybe someone can give me an explanation that can convince me....

18 comments:

  1. I wrote B. There is no method called doSomethingStupid in B. In fact, foo calls doSomethingImportant. doSomethingImportant does things that keeps B intact (consistent). Since you want C to share B's implementation of A, you better take it all. I'm happy Java doesn't let you (or people like you) cherry pick from my B. If java allowed it, then doSomethingImportant would not get called, your C would be broken, and you would blame me.

    Regards,
    Anonymous.

    ReplyDelete
  2. The problem here is that this can fairly easily lead to illegal bytecode. e.g. what does super.super mean for a basic class that extends Object.

    Perhaps the validity of super.super (or super.super.super) could be detected at runtime with a verification check to ensure a sufficiently deep hierarchy(??) but that might have other ramifications.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. I can't see a single usecase for when a call to a super.super.method is required. If you really need this I guess your design is wrong.

    ReplyDelete
  5. Why don't you just override doSomethingStupid() ?

    ReplyDelete
  6. here is an interesting read.

    ReplyDelete
  7. I understand that this is a bit of a contrived example, but why not just extend A? Or break b::foo into two methods such that you could have
    class b {
    foo() {
    stuff;
    foostupid();
    }
    foostupid()
    }
    class c {
    foo() {
    super.foo();
    }
    @override
    foostupid() {
    // noop
    }
    }

    ReplyDelete
  8. I understand that this is a bit of a contrived example, but why not just extend A? Or break b::foo into two methods such that you could have
    class b {
    foo() {
    stuff;
    foostupid();
    }
    foostupid()
    }
    class c {
    foo() {
    super.foo();
    }
    @override
    foostupid() {
    // noop
    }
    }

    ReplyDelete
  9. If you do not implement B::foo() super .foo() will call A::foo(). What would be super.super in such 'holey' implementation?

    ReplyDelete
  10. It's probably because there was no strong use-case for super.super when the language was written. This use-case (man-in-the-muddle? dumb dad?) isn't altogether compelling.

    A super call uses a special bytecode that knows to, well, reference the super class, rather than doing the usual method lookup. It's a special code that does just that (rather than a bytecode to load the superclass as the refering instance/type, followed by a bytecode to invoke a method on said type/instance). So (to be conclusory) super.super doesn't work because it doesn't translate to bytecode the way super.foo() does. Reference to this and super get to be treated specially (because they're assumed to be first on the stack).

    - Less conclusory: (1) where should it end? super.super.super.super? Should the programmer of a module know about implicit properties of a module? Each class knows about its superclass, but not about the super-super class. For access and reference relationships to be easy to use and compute, they need to be binary. These relational ones are hard for most people to understand. (2) The class reference for a method call has to be determinable at compile-time to write the correct signature. Having super.super would mean following the chain up. Should this happen at run-time or compile-time? If compile-time, then you have to extend the notion of binary-compatible quite a ways to include possible super.super references. If run-time, then you can't do the special things that super calls allow. Java would certainly be simpler if there was only one way to invoke things, but there are different bytecodes for invoking static methods, constructors, super calls, etc., all to take advantage of possible efficiencies in the process of loading and executing code.

    ReplyDelete
  11. When you extend a class, you know you are extending a class. Thus calling super is legal and sometimes neccisary because you might want to keep/extend some original behaviour.

    But you don't know/care if this super class is also a sub-class of any other class.

    If this doesn't make sence, think of it this way; If you want to circumvent the behaviour of the class you are extending and reach a super class lower. Shouldn't you just extend that class.

    ReplyDelete
  12. Perhaps it is related to security. If part of doSomethingStupid() is validateLicense(), they probably don't appreciate you sidestepping the process.

    If I were writing textbooks on Java design, I'd advise you to change the design such that C extends A but not B. Or if you can edit class B, make doSomethingStupid() a protected method that C overloads with a NoOp.

    ReplyDelete
  13. You can do it, but have to use different syntax.

    ((A) this).foo();

    Take a look at 15.11.2 in the Java 1.4 language spec.

    ReplyDelete
  14. Sorry, I was too hasty posting the previous comment. I see that only worked for field access, not method invocations and would actually produce an infinite recursion.

    ReplyDelete
  15. Think about what would happen, if B had some private final fields. When would they be initialized?

    ReplyDelete
  16. This comment has been removed by a blog administrator.

    ReplyDelete
  17. I can understand the use-case for super.super!
    I have a related Problem trying to Override the toString Method:

    Class GeometricO
    public String toString{
    return "width"+this.width+"height...";
    }
    }
    Class Rectangle extends GeometricO{
    public String toString{
    return "Rectangle("+super.toString+")";
    }
    }
    Class ComplexRectangle
    public String toString{
    return "ComplexRectangle(("+this.Alpha+this.Beta" + ")" + this..."+super.super.toString();
    }
    }

    this Object is an Complex Rectangle, and the Rectangle.toString Method is well designed, but I don't wanna have the String "Rectangle" in the ComplexRectangle.toString method to appear.
    My Solution to write another:
    Rectangle.toString(boolean SuperSuper){
    if (SuperSuper){
    return super.toString
    } else {
    return Rectangle.toString()
    }
    }
    is IMHO very UGLY!

    ReplyDelete
  18. I'm also not happy with the "do not cherry pick from B" argument. When using third-party libraries, you'd perhaps like them to be completely customizable for your purposes, but in practice you may only want some of their functionality.

    Here is a case in point:

    I want to use a plotting library to produce plots within my application. The only functionality I need from this library is its plot rendering. However, it also includes mouse behaviours as part of its functions, and in doing so overrides mouse handling methods from Component to defer handling to a proxy panel (which is of course private).

    In this case, I have a class B which performs the plotting I want, but also overrides the default mouse behaviour of Swing, which I want to have full control over. Thus, it would be great to call super.super and override their override, so to speak.

    This is not the first time this has happened to me. The easiest solution, of course, is to modify the third-party code; i.e., class B. But this is not an ideal solution; usually you want to use the third-party library as-is.

    I'm not offering a solution; just pointing out that practical use cases do exist. Good arguments have been made here for the illegality of a super.super call; but I think these are all addressable.

    ReplyDelete