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?