Is this valid Java?
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static String f(List<String> list) {
System.out.println("strings");
return null;
}
public static Integer f(List<Integer> list) {
System.out.println("numbers");
return null;
}
public static void main(String[] args) {
f(Arrays.asList("asdf"));
f(Arrays.asList(123));
}
}
- Eclipse 3.5 says yes
- Eclipse 3.6 says no
- Intellij 9 says yes
- Sun javac 1.6.0_20 says yes
- GCJ 4.4.3 says yes
- GWT compiler says yes
- Crowd at my previous Stackoverflow question says no
My java theory understanding says no!
It would be interesting to know what the JLS is saying about it.
It depends upon how you wish to call these methods. If you wish to call these methods from other Java source code, then it is considered invalid for reasons illustrated in Edwin's answer. This is a limitation of the Java Language.
However, not all classes need to be generated from Java source code (consider all the languages that use the JVM as their runtime: JRuby, Jython, etc...). At the bytecode level, the JVM can disambiguate the two methods because the bytecode instructions specify the return type they are expecting. For example, here is a class written in Jasmin that can call either of these methods:
I compile it to a class file using the following command:
And call it using:
Behold, the output is:
Update
Simon posted an example program that calls these methods:
Here is the Java bytecode generated:
It turns out the Sun compiler is generating the bytecode necessary to disambiguate the methods (see instructions 12 and 31 in the last method).
Update #2
The Java Language Specification suggests that this may, in fact, be valid Java source code. On page 449 (§15.12 Method Invocation Expressions) we see this:
Unless I am mistaken, this behavior should only apply to methods declared as abstract, though...
Update #3
Thanks to ILMTitan's comment:
From what I can tell the .class file can hold both methods since the method descriptor holds the parameters, as well as the return type. If the return type would be the same, then the descriptors would be the same and the methods would be indistinguishable after the type erasure (hence it doesn't work with void either). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035
Now, calling the method with invoke_virtual requires the method descriptor, so you can in fact say which one of the methods you want to call, so it seems that all those compilers, which still have the generic information, simply put the descriptor for the method that matches the generic type of the parameter, so then it's hardcoded in the bytecode which method to call (as distinguished by their descriptors, or more exactly by the return type in those descriptors), even if the parameter is now a List, without generic information.
While I find this practice a little questionable, I must say I find it kind of cool that you can do this, and think that generics should have been designed to be able to work like this in the first place (yes, I know that would create problems with backwards compatibility).
--- Edited in response to comments below ---
Ok, so it is valid Java, but it shouldn't be. The key is that it's not really relying on the return type, but on the erased Generics parameter.
This wouldn't work on a non-static method, and is explicitly forbidden on a non-static method. Attempting to do so in a class would fail due to the extra issues, the first being that a typical class isn't final as the class Class is.
It's an inconsistency in an otherwise rather consistent language. TI'll go out on a limb and say that it should be illegal, even if technically allowed. It doesn't really add anything to the readability of the language, and it adds scant little to the ability to solve meaningful problems. The only meaningful problem it seems to solve is whether you are familiar enough with the language to know when it's core tenets seem to be violated by the language's internal inconsistencies in resolving type erasure, generics, and the resulting method signatures.
Definitely code to be avoided, as it is trivial to solve the same problem in any number of more meaningful ways, and the only benefit is to see if the reviewer/extender knows a dusty dirty corner of the language spec.
--- Original Post follows ---
While the compilers might have allowed it, the answer is still no.
Erasure will turn both the List<String> and List<Integer> into an unadorned List. That means that both of your "f" methods will have the same signature but different return types. Return type can't be used to differentiate methods, because doing so will fail when you return into a common super-type; like:
Have you tried capturing the returned values into variables? Perhaps the compiler has optimized out things in such a way that it's not stepping on the right error code.
Also working (with sun java 1.6.0_16 this time) is
Looks like the compiler chooses the most specific method based on the generics.
}
Output:
One questioned that hasn't been answered is: why does it only trigger a compile error in Eclipse 3.6?
Here's why: it's a feature.