Reputation: 3206
Why method f() is ambiguous in Derived?
class Base<T> {
void f(T arg) { }
}
class Derived<S extends CharSequence> extends Base<String> {
void f(S arg) { } // compiles OK so far !!!
}
Derived<String> obj = new Derived<>();
obj.f("hi"); // c.ERR: The method f(String) is ambiguous for the type Derived<String>
BUT:
Derived<StringBuffer> obj = new Derived<>();
obj.f("hi"); // OK! Overloaded methods resolved?
obj.f(new StringBuffer()); // OK! Overloaded methods resolved?
void f(String)
is inherited from Base (void f(Object) at runtime
), same void f(String)
is generated in Derived (void f(Object)
at runtime).
Please help me to pinpoint when and where is the conflict?
So far I tend to think that proper method to call is chosen from all possible overloaded methods at compile-time only (!), and Derived extends Base means to the compiler that at compile-time Derived has two methods with same signatures: Base.f(String) and Derived.f(String) which makes f("hi") call ambiguous (both overloaded versions apply).
Overriding in this case is not possible (@Override in Derived really gives compile error) because same signature (same argument) cannot be guaranteed, and it is just one lucky coincidence for ...extends Base where override could be possible, but JLS would flag compile error.
The fact that real runtime signatures of methods (as seen by JVM after compilation) are void Base.f(Object)
and void f(CharSequence)
is totally irrelevant to overload resolution because that resolution happens only at compile time!
Java Language Specification:
When a method is invoked (§15.12), the number of actual arguments (and any explicit type arguments) and the compile-time types of the arguments are used, at compile time, to determine the signature of the method that will be invoked (§15.12.2). If the method that is to be invoked is an instance method, the actual method to be invoked will be determined at run time, using dynamic method lookup (§15.12.4).
P.S. Are Base.f() and Derived.f() always overloaded? But how then? As void f(Object) at runtime
vs void f(CharSequence) at runtime
? If they are overridden in case of new Derived, then are they overloaded or overridden depending on what type parameter Derived given during instantiation? Is that possible?
Upvotes: 2
Views: 287
Reputation: 1199
For the given case of Base
class,
class Base<T> {
void f(T arg) { }
}
Your Derived
class,
class Derived<S extends CharSequence> extends Base<String> {
void f(S arg) { }
}
doesn't override the f()
method, but rather overloads the f()
method because of type erasure.
Hence, the Derived
class contains 2 overloads of f()
,
Base
class: void f(Object arg)
void f(CharSequence arg)
Since, in java Generics provide only compilte-time type safety, compiler makes full use of this to infer correct overload of f()
on a parameterized Derived<SomeSubClassOfCharSequence>
depending on the argument passed.
This is what happened in the case of
Derived<StringBuffer> obj = new Derived<>();
obj.f("hi"); // OK! Overloaded methods resolved?
obj.f(new StringBuffer()); // OK! Overloaded methods resolved?
But, in the case of
Derived<String> obj = new Derived<>();
obj.f("hi");
The compiler is not able to infer the one overload over the other, hence the ambiguity.
To answer yourquestions:
f()
produce different method signatures.Base.f()
in the Derived.f() method, aka bridge methods. But your case is not one of these scenarios.Upvotes: 1
Reputation: 5207
The definition of the class Base
means that method f()
can accept parameters of any type. It can be not only String, but also Integer, Long, and actually an instance of any class. What class, it depends on generic parameter used when an instance of class Base
is created.
The definition of the class Derived
allows to pass instances of any sub-classes of CharSequence
.
It is very important that the definition of Derived
does not mean that its generic parameter S
is somehow related to the generic parameter T
of the parent class. Such definition means, that the method f()
in Derived
does not override method f()
from the parent class. Thus, the compiler sees two methods.
What could you do? It depends on what is your goal. But one solution may be following:
class Base<T> {
void f(T arg) { }
}
class Derived<S extends CharSequence> extends Base<S> {
void f(S arg) { }
}
Derived<String> obj = new Derived<>();
obj.f("hi");
Upvotes: 2