I'm not sure that I understand overloading of generic methods.
The spec says in section 5.6.1, page 15:
"Overload resolution changes in several ways due to genericity and covariant return types. In particular: The existing specification assumes that if there are multiple candidate methods with the same signature, they all have the same return type. This is no longer the case.
...
If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen."
Why is the following illegal then?
publicclass Test {
privatestaticclass someClass implements Cloneable {
}
privatestatic <T> T f() {
returnnull;
}
privatestatic <T extends someClass> T f() {
returnnull;
}
privatestatic <T extends Cloneable> T f() {
returnnull;
}
privatestaticvoid test1() {
someClass d = null;
String s = null;
Cloneable c = null;
d = f();
s = f();
c = f();
f();
}
publicstaticvoid main(String[] args) {
test1();
}
}
The compiler complains about ambiguous method calls when I invoke f(), no matter how I invoke it. Why doesn't it pick the most specific method?
In the example of
someClass d = f();
the compiler must infer the type parameter, which in this case would be T:=someClass. And then I would think that
<T extends someClass> T f()
is the most specific method.
The spec describes what "more specific" means, but only for methods that have arguments, as far as I understand it. What does it mean for methods without arguments?
In the example of
String s = f();
the compiler would infer T:=String. And then there is only one viable candidate, namely
<T> T f()
. The compiler does not even have to decide which candidate is the most specific one, and still it complains about an ambiguity. Why?
I would perhaps understand it if the compiler rejected the method definitions as duplicates in the first place, because you cannot overload methods that only differ in their return types. But that's not what it does.
I'm sure I'm missing something essential here. What is it? Does anybody have any idea?
Perhaps that should be made more explicit than simply
ordering the sections of the spec.
There is another detail missing in the spec: does the compiler, during the type inference phase, infer types that conform to the bounds? or, does it infer types (regardless of the bounds) and checks against the bounds afterwards? It makes a difference ...
The reality is that your original statement had nothing to do with generics specifically. You cannot differentiate methods by return type, only by parameters. Therefore your example rewritten as the following would still fail in compilation:
publicclass foo {
A f() {...}
B f() {...}
C f() {...}
}
Since the compiler for templates would boil your example down to that, you would get an ambiguity in the same manner as if you wrote that without templates.
The reality is that your original statement had
nothing to do with generics specifically. You cannot
differentiate methods by return type, only by
parameters.
This is true for regular non-generic methods, but not for parameterized methods.
The specification says (page 9, section 3.1):
"It is illegal to declare two methods with the same name and the same argument types in a class. The definition of "having the same argument types" is extended to generic methods as follows:
Two method declarations M and N have the same arguments types if either none of them has type parameters and their argument types agree, of they have the same number of type parameters, say <A1,...,An> for M and <B1,...,Bn> for N, and after renaming each occurence of a Bi in N's type to Ai the bounds of corresponding type variable are the same and the arguments tpyes of M and N agree."
In my examples the parameterized methods have different bounds, which means, that under the definition in the specification they do NOT "have the same argument types". Hence it is expressly allowed to declare these methods in the same class.
privatestatic <T> T f() {
returnnull;
}
privatestatic <T extends someClass> T f() {
returnnull;
}
privatestatic <T extends Cloneable> T f() {
returnnull;
}
In my examples the parameterized methods have
different bounds, which means, that under the
definition in the specification they do NOT "have the
same argument types". Hence it is expressly allowed
to declare these methods in the same class.
Not disallowed by that section of the spec, agreed. Disallowed by the constraints in section 6.2 on the bottom of page 19.
"It is a compile time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following conditions hold:
? m1 and m2 have the same name.
? m2 is accessible from T.
? m1 and m2 have different signatures.
? m1 or some method m1 overrides (directly or indirectly) has the same erasure as m2 or some
method m2 overrides (directly or indirectly)."
My methods have the same name and all three are accessible. But they do NOT have different signatures and the do NOT have the same erasure. Why would they be disallowed?
Is this another specification error? Or am I misreading anything?
What is the signature of a parameterized method? The spec does not define the term "signature" for a generic method. But the JLS has a definition for non-generic methods:
"8.4.2 Method Signature: The signature of a method consists of the name of the method and the number and types of formal parameters to the method. A class may not declare two methods with the same signature, or a compile-time error occurs."
Provided that this definition still holds I would think the signature of all my methods is: "f()" [ Is the type argument list part of the signature of a generic method? In that case, my methods would indeed have different signatures. ]
What is the erasure of a parameterized method? The spec does not explicitly define the term "erasure" for a generic method, but I guess the beginning of section 6.2 is supposed to serve as a definition:
"Each method T m(T1, . . . , Tn) throws S1, . . . , Sm is translated to a method with the same name whose return type, argument types, and thrown types are the erasures of the corresponding types in the original method."
Seemingly the return type is part of the erasure. According to that definition the three method have different erasures:
Object f()
someClass f()
Cloneable f()
Also, there is section 5.6.1:
"Overload resolution changes in several ways due to genericity and covariant return types. In particular:
? The existing specification assumes that if there are multiple candidate methods with the same signature, they all have the same return type. This is no longer the case.
? It is possible that a particular instantiation would have several concrete methods with the same signature. This was not possible before."
I don't know, but I read this as: yes, you can have several methods with the same signature (as defined in the JLS) and different return types.
OK, I will try to be specific. The definition of generics does not change the fact that you have several methods whose signature is identical. The return type of a method is NOT a component of its signature; therefore, the return type can not be used to select a method, nor would it be feasible to do so. In order to accomplish that, the language would have to inspect the context in which a method is called and take appropriate action based upon that context. In other words, selection of the behavior on the right hand of an assignment would depend on the contents of the left hand of the assignment.
In a simple case such as the following code, the question is easy:
Integer x = foo.f();
In this case we merely look at the left side and see that it wants an Integer and go with the method that returns an integer. Similar results would occur if x was of type Object or Number.
However, it gets more complex with nested expressions.
Integer x = someObj.g(someObj.y * Math.abs(someObj.floatValue() *
anotherObj.g(foo.f()));
Now we have a royal mess to decode. Assuming the worst case where each of the objects has similar overloaded methods with varying return types but the same signature as well as overloaded methods with the varying signatures, we now have to decode the context in which the method is called to know which method to call. In the case of foo, the f() method call will depend upon the needs of anotherObj.g(); however if g() has methods to accept an Integer or a Number type, then things get ambiguous because f() has methods that produce an Integer and one that produces a Number. What is more, you cant count on the g() that takes an Integer as being fundamentally the same as the g() that takes a Number. So now which one do you pick? Furthermore if both g() methods that take an Integer and one that takes a Number are further overloaded by return type then the mess propagates into the call chain like a cancer. Even if you could get it to work, it would run like a three legged dog in quicksand.
The only legitimate solution is to define methods that overload based upon parameter type. In this manner the indications are clear as to which method to select. And thus all overloadable languages select by parameter instead of return type.
Back to your question, you are attempting to overload by return type. Dont let the extra keywords and angle brackets confuse you. The parameters are identical. In fact, the compiler will boil out all the extra stuff and end up with A, B, C type classes as I stated in my example.
Therefore, when you boil it down to the generated byte code, it violates the rules of java and wont run. Once you boil out the extra syntax, anything that wont run in a normal JVM wont run under 1.5. In fact, the stuff for generics ALL happens at compile time and the end result byte code is pre-generics comptible; hence the reason the changes for generics are in the compiler and not the JVM.
publicclass Foo {
public Integer x() { ... }
public Number x() { ... }
public Object x() { ... }
}
publicclass Bar {
publicvoid y(final Integer value) { ... }
publicvoid y(final Number value) {
...
// very different than first y(Integer and y(Object)
}
publicvoid y(final Object value) {
...
// very different than y(Integer) and y(Number) method
}
}
Oh, no, your examples illustrate the point very well. Thank you.
The "royal mess" from which the compiler can't do overload resolution is also the reason why method calls are not considered "context" for type argument inference, I guess. But the compiler performs type argument inference when the context is an assignment. Why doesn't it do overload resolution in that case, too? My examples are fairly simple; it's not that the compiler is incapable of figuring out which method I intend to invoke.
Wouldn't it be more consistent and easier to understand if the context for overload resolution were the same as the context for type argument inference? Isn't it a perfectly natural expectation? When a programmer sees that the compiler can figure out the type argument of a parameterized method from its occurrence in an assignment, (s)he might wonder why the compiler can't choose the right method from a set of overloads in the same situation. But that's an aside.
The real question is: what is the spec trying to say? The spec expressly talks of modified overload resolution rules due to genericity (see section 5.6.1) and says: "It is possible that a particular instantiation would have several concrete methods with the same signature. This was not possible before."
I'm not sure what a "concrete" method is. I guess, it is a particular instantiation of a generic method (a generic method being either a parameterized method and/or a non-static method of a parameterized class). My example has several concrete methods with the same signature. Why is it illegal?
I understand your explanations, but I do not understand how they relate to the Generics spec.
The compiler complains about ambiguous method calls
when I invoke f(), no matter how I invoke it. Why
doesn't it pick the most specific method?
Because none is most specific.
In the example of
someClass d = f();
the compiler must infer the type parameter, which in
this case would be T:=someClass. And then I would
think that
<T extends someClass> T f()
is the most specific method.
I don't see any way to conclude that from the spec.
There is not concept of "most specific" in the JSR14 spec.
Perhaps you mean maximally specific.
The spec describes what "more specific" means, but
only for methods that have arguments, as far as I
understand it. What does it mean for methods without
arguments?
The definition generalizes to zero parameters without
a problem. In this case each is more specific and strictly
more specific than the other, but none is maximally specific.
In the example of
String s = f();
the
compiler would infer T:=String. And then there is
only one viable candidate, namely
<T> T f()
. The compiler does not even have to decide
which candidate is the most specific one, and still
it complains about an ambiguity. Why?
Because the test that an inferred type parameter is in
bounds is not part of the "applicable" test. Perhaps it
should be. File a spec bug.