21

The following code is considered invalid by the compiler:

class Foo {
    void foo(String foo) { ... }
}

class Bar extends Foo {
    @Override
    void foo(Object foo) { ... }
}

I think that this is described in the JLS 8.4.8.1: "The signature of m1 is a subsignature (§8.4.2) of the signature of m2." and in 8.4.2: "the bounds of corresponding type variables are the same".

My question is: why can't the parameter in the subtype (Bar) be a supertype of the parameter in the supertype (Foo). In the example Object is a supertype of String. As far as I can see allowing this wouldn't violate the Liskov Substitution Principle.

Is there a scenario in which allowing this would break the code or is it a limitation of the current JLS?

3
  • 3
    Foo x = new Bar(); x.foo("hello"); which foo should be called?
    – robbie_c
    Commented Jul 5, 2013 at 9:40
  • 1
    to solve the problem of ambiguity , this ls not allowed, thats it ! Commented Jul 5, 2013 at 9:41
  • 4
    @robbie_c: I would expect the foo method from Bar to be called (because of overriding + dynamic dispatch). Commented Jul 5, 2013 at 9:55

7 Answers 7

12

Suppose you could do that. Now your superclass looks like this:

class Foo {
    void foo(String foo) { ... }
    void foo(Number foo) { ... }
}

and your subclass now:

class Bar extends Foo {
    @Override
    void foo(Object foo) { ... }
}

The language probably could allow such a thing (and just dispatch both Foo.foo(String) and Foo.foo(Number) to Bar.foo(Object)), but apparently the design decision for Java here is that one method only can override exactly one other method.

[Edit]

As dasblinkenlight said in his answer, one can have a foo(Object) without the @Override, but this just overloads the foo functions, and does not override them. When calling, java chooses the most specific method, so foo("Hello World") would always get dispatched to the foo(String) method.

2
  • 1
    Correct, this is an ambiguity and I fully expect that in this case the compiler would complain. In the unambiguous case (as given in the question) however I would expect it to work (there are other such cases when the unambiguous case works and when it becomes ambiguous, it stops working/compiling). One possible reason I can see from your example: there would be no way to disambiguate the example shown (other than introducing new syntax). So maybe this is the reason. Commented Jul 5, 2013 at 10:54
  • 1
    @Cd-MaN One question comes to mind: Why is there no possibility to specify the method you want to override? I think that is part of your question? Commented Jul 10, 2013 at 8:46
9
+300

(A rewrite, from a different angle ... my original answer contained an error. :( )

why can't the parameter in the subtype (Bar) be a supertype of the parameter in the supertype (Foo).

I believe that technically it could, and it wouldn't break ancestor contracts following type substition (Liskov Substitution Principle).

  • Types are passed-by-value (including reference types).
  • The caller can never be forced to deal with different parameter types than what it passes in.
  • A method body can swap a parameter type, but can't return it back to the caller (no such thing as 'output parameter types').
  • If your proposal was allowed, and a call was made to the ancestor method signature, a decendent class could override the method with broader types, but it's still impossible to return a broader type than what the caller sets.
  • The override could never break a client that uses the narrow ancestor method contract.

From my analysis below, I surmise/guess the rationale for not allowing your scenario:

  • Performance-related: allowing broader types in override would impact runtime performance, a major issue
  • Functionality-related: it only adds a minor amount of functionality. As it stands, you could add your 'broader method' as an overloaded method without overriding. Then you could additionally override the original method with an exact signature match. The net result: you achieve something quite similar, functionally.

Compiler Requirements for Method Override - JLS 7

The compiler's required to act accordancing to your experience. 8.4 Method Declarations:

A method in a subclass can override a method in an ancestor class iff:

  • the method names are identical (8.4.2 and 8.4.8.1)
  • the method parameters have identical types, after erasure of generic type parameters (8.4.2 and 8.4.8.1)
  • the return type is type-substitutable for the return type in the ancestor class, i.e. the same type or narrower (8.4.8.3)

    Note: subsignature does not mean the overriding method uses subtypes of the overridden method. The overriding method is said to have a subsignature of the overridden method when the overriding method has exactly the same type signature except that generic types and the corresponding raw types are considered equivalent.


Compiler v Runtime Processing for Method Matching & Invocation

There's a performance hit matching method signatures via polymorphic type matching. By restricting override method signatures to exact match of ancestor, the JLS moves much of this processing to compile time. 15.12 Method Invocation Expressions - summarised:

  1. Determine Class or Interface to Search (Compile-Time Determination)

    • Obtain the base type T, on which the method is being invoked.
    • This is the reference type declared to the compiler and not the runtime type (where a subtype may be substituted).
  2. Determine Method Signature (Compile-Time Determination)

    • Search the compile-time base type T, for applicable methods matching the name and parameter & return types consistent with the call.
      • resolve generic parameters for T, either explicitly passed or implicitly inferred from the types of invocation method arguments
      • stage 1: methods applicable via consistent types/subtypes ('subtyping')
      • stage 2: methods applicable via automatic boxing/unboxing plus subtyping
      • stage 3: methods applicable via automatic boxing/unboxing plus subtyping plus variable 'arity' parameters
      • determine the most specific matching method signature (i.e. the method signature that could successfully be passed to all other matching method signatures); if none: compiler/ambiguity error
  3. Check: Is the Chosen Method Appropriate? (Compile-Time Determination)

  4. Evaluation of Method Invocation (Runtime Determination)

    • Determine runtime target reference type
    • Evaluate arguments
    • Check accessibility of method
    • Locate method - an exact signature match against the compile-time matched signature
    • Invoke

Performance Hit

The breakdown, in terms of text in the JLS:

Step 1: 5% Step 2: 60% Step 3: 5% Step 4: 30%

Not only is Step 2 volumous in text, it's surprisingly complex. It has complex conditions and many expensive test conditions/searches. It's advantageous to maximise the compiler's execution of the more complex and slower processing here. If this was done at runtime, there would be a drag on performance, because it would occur for each method invocation.

Step 4 still has significant processing, but it is as streamlined as possible. Reading through 15.12.4, it contains no processing steps that could be moved to compile time, without forcing the runtime type to exactly match the compile-time type. Not only that, but it does a simple exact match on the method signature, rather than a complex "ancestor type match"

3
  • Thank you for the very detailed response. I'm still feeling fuzzy in this issue though - if I understand correctly you say that the parameters can be input and/or output and this is the reason why it must have exactly the same types. Java doesn't have "pass by reference" though - all the parameters are "pass by value", it just so happens that some values are references :-). As such, I don't really think that you can consider some parameters as being of "output type". Commented Jul 8, 2013 at 13:25
  • Also, lets say that m1 has a signature of m1(String[] x) (a common idiom used for output). If in a subclass I override with m1(Object[] x), I can still assign a String into X, thus - as far as I can tell - the contract is not violated by giving more generic types to the subclass parameters. Commented Jul 8, 2013 at 13:27
  • Good feedback. You're right re "pass by value" - it means there can only be an "output type" for the return type. So my answer's wrong - back to drawing board. I've rexamined the spec and redrafted my answer. The spec includes no rationale on this one, so answering involves some level of guessing...
    – Glen Best
    Commented Jul 9, 2013 at 5:58
5

Java annotations cannot change the way the compiler generates byte code from your classes. You use annotations to tell the compiler how you think your program should be interpreted, and report errors when compiler's interpretation does not match your intentions. However, you cannot use annotations to force the compiler to produce code with different semantics.

When your subclass declare a method with the same name and parameter count as in its superclass, Java must decide between two possibilities:

  • You want the method in the subclass to override the method in the superclass, or
  • You want the method in the subclass to overload the method in the superclass.

If Java allowed foo(Object) to override foo(String) the language would have to introduce an alternative syntax for indicating an intent to overload a method. For example, they could have done it in a way similar to C#'s new and override in method declarations. For whatever reason, however, the designers decided against this new syntax, leaving the language with the rules specified in JLS 8.4.8.1.

Note that the current design lets you implement the functionality of an override by forwarding the call from the overloaded function. In your case, that would mean calling Bar.foo(Object) from Foo.foo(String) like this:

class Foo {
    public void foo(String foo) { ... }
}

class Bar extends Foo {
    @Override
    public void foo(String foo) { this.foo((Object)foo); }
    public void foo(Object foo) { ... }
}

Here is a demo on ideone.

4
  • foo(String) and foo(Object) is ambiguous, since all Strings are Objects. I am pretty sure you can't have both with the same name.
    – kutschkem
    Commented Jul 9, 2013 at 11:23
  • 1
    @kutschkem Well, you are wrong: it works perfectly fine (link). Java has pretty complicated rules to decide among these. Look up "Choosing the Most Specific Method" for details. Commented Jul 9, 2013 at 12:13
  • wow i didn't know that. +1, since i think this design decision by java people is the real reason why you can't ever override foo(String) with foo(Object). so... that means the only thing the compiler is complaining about in the OPs question is the Override annotation?
    – kutschkem
    Commented Jul 9, 2013 at 12:54
  • 1
    @kutschkem Exactly, that's my point: the compiler complains about the @Override annotation, not about the function itself. Commented Jul 9, 2013 at 12:58
2

The answer is plain simple, in Java, for method overriding, you must have the exact signature of the super type. However, if you remove the @Override annotation, your method would be overloaded and your code won't break. This is a Java implementation that ensures that you mean the method implementation should override the implementation of the super type.

Method overriding works in the following way.

class Foo{ //Super Class

  void foo(String string){

    // Your implementation here
  }
}

class Bar extends Foo{

  @Override
  void foo(String string){
    super(); //This method is implied when not explicitly stated in the method but the @Override annotation is present.
    // Your implementation here
  }

  // An overloaded method
  void foo(Object object){
    // Your implementation here
  }
}

The methods shown above are both correct and their implementation can vary.

I hope this helps you.

1

To answer the question in the title of the post What is the reasoning behind not allowing supertypes on Java method overrides?:

The designers of Java wanted a simple object oriented language and they specifically rejected features of C++ where, in their opinion, the complexity/pitfalls of the feature wasn't worth the benefit. What you describe may have fallen into this category where the designers chose to design/specify out the feature.

0

The problem with your code is that you are telling compiler that foo method in Bar class is overridden from parent class of Bar, i.e. Foo. Since overridden methods must have same signature, but in your case, according to syntax it is an overloaded method as you have changed the parameter in foo method of Bar class.

0

If you can override with superclasses, why not with subclasses as well?

Consider the following:

class Foo {
  void foo(String foo) { ... }
}

class Bar extends Foo {
  @Override
  void foo(Object foo) { ... }
}
class Another extends Bar {
  @Override
  void foo(Number foo) { ... }
}

Now you have succesfully overriden an method whose original parameter was a String to accept a Number. Inadvisable to say the least...

Instead, the intended results may be replicated by using overloading and the following, more explicit, code:

class Foo {
    void foo(String foo) { ... }
}

class Bar extends Foo {
    @Override
    private void foo(String foo) { ... }
    void foo(Object foo) { ... }
}
class Another extends Bar {
    @Override
    private void foo(Object foo) { ... }
    void foo(Number foo) { ... }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.