Is this valid Java?
Is this valid Java?
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static String f(List<String> list) {
开发者_StackOverflow 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:
.class public CallAmbiguousMethod
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit stack 3
.limit locals 1
; Call the method that returns String
aconst_null
invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String;
; Call the method that returns Integer
aconst_null
invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer;
return
.end method
I compile it to a class file using the following command:
java -jar jasmin.jar CallAmbiguousMethod.j
And call it using:
java CallAmbiguousMethod
Behold, the output is:
> java CallAmbiguousMethod strings numbers
Update
Simon posted an example program that calls these methods:
import java.util.Arrays;
import java.util.List;
class RealyCompilesAndRunsFine {
public static String f(List<String> list) {
return list.get(0);
}
public static Integer f(List<Integer> list) {
return list.get(0);
}
public static void main(String[] args) {
final String string = f(Arrays.asList("asdf"));
final Integer integer = f(Arrays.asList(123));
System.out.println(string);
System.out.println(integer);
}
}
Here is the Java bytecode generated:
>javap -c RealyCompilesAndRunsFine Compiled from "RealyCompilesAndRunsFine.java" class RealyCompilesAndRunsFine extends java.lang.Object{ RealyCompilesAndRunsFine(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public static java.lang.String f(java.util.List); Code: 0: aload_0 1: iconst_0 2: invokeinterface #2, 2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 7: checkcast #3; //class java/lang/String 10: areturn public static java.lang.Integer f(java.util.List); Code: 0: aload_0 1: iconst_0 2: invokeinterface #2, 2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 7: checkcast #4; //class java/lang/Integer 10: areturn public static void main(java.lang.String[]); Code: 0: iconst_1 1: anewarray #3; //class java/lang/String 4: dup 5: iconst_0 6: ldc #5; //String asdf 8: aastore 9: invokestatic #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 12: invokestatic #7; //Method f:(Ljava/util/List;)Ljava/lang/String; 15: astore_1 16: iconst_1 17: anewarray #4; //class java/lang/Integer 20: dup 21: iconst_0 22: bipush 123 24: invokestatic #8; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 27: aastore 28: invokestatic #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 31: invokestatic #9; //Method f:(Ljava/util/List;)Ljava/lang/Integer; 34: astore_2 35: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream; 38: aload_1 39: invokevirtual #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 42: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream; 45: aload_2 46: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 49: return
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:
It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:
- If all the maximally specific methods have override-equivalent (§8.4.2) signatures, then:
- If exactly one of the maximally specific methods is not declared abstract, it is the most specific method.
- Otherwise, if all the maximally specific methods are declared abstract, and the signatures of all of the maximally specific methods have the same erasure (§4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type. However, the most specific method is considered to throw a checked exception if and only if that exception or its erasure is declared in the throws clauses of each of the maximally specific methods.
- Otherwise, we say that the method invocation is ambiguous, and a compiletime error occurs.
Unless I am mistaken, this behavior should only apply to methods declared as abstract, though...
Update #3
Thanks to ILMTitan's comment:
@Adam Paynter: Your bolded text does not matter, because it is only a case when two methods are override-equivalent, which Dan showed was not the case. Thus, the determining factor must be if the JLS takes generic types into account when determining most specific method. – ILMTitan
--- 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:
Object o = f(Arrays.asList("asdf"));
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.
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.
In javac 7, two methods are considered duplicates (or a name clash error) regardless of their return types.
This behaviour is now more consistent with javac 1.5, which reported name clash errors on methods and ignored their return types. Only in 1.6 was a change made that included return types when detecting duplicate methods.
We have decided to make this change across all compliance levels (1.5, 1.6, 1.7) in the 3.6 release so users will not be surprised by the change if they compile their code using javac 7.
Well, if I understand bullet point three correctly in the first list from section 8.4.2 of the spec, it says your f() methods have the same signature:
http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649
It's the spec that really answers this question, and not the observed behavior of compiler X or IDE X. All we can say by looking at the tools is how the author of the tool interpreted the spec.
If we apply bullet three, we get:
... public static String f(List<String> list) { System.out.println("strings"); return null; } public static Integer f(List<String> list) { System.out.println("numbers"); return null; } ...
and the signatures match, so there's a collision and the code shouldn't compile.
It's valid vased on the specification.
The signature of a method
m1
is a subsignature of the signature of a methodm2
if either
m2
has the same signature asm1
, orthe signature of
m1
is the same as the erasure of the signature ofm2
.
So these are not subsignatures of eachother because the erasure of List<String>
is not List<Integer>
and vice versa.
Two method signatures
m1
andm2
are override-equivalent iff eitherm1
is a subsignature ofm2
orm2
is a subsignature ofm1
.
So these two are not override-equivalent (note the iff). And the rule for overloading is:
If two methods of a class (whether both declared in the same class, or both inherited by a class, or one declared and one inherited) have the same name but signatures that are not override-equivalent, then the method name is said to be overloaded.
Therefore, these two methods are overloaded and everything should work.
Also working (with sun java 1.6.0_16 this time) is
import java.util.Arrays;
import java.util.List;
class RealyCompilesAndRunsFine {
public static String f(List<String> list) {
return list.get(0);
}
public static Integer f(List<Integer> list) {
return list.get(0);
}
public static void main(String[] args) {
final String string = f(Arrays.asList("asdf"));
final Integer integer = f(Arrays.asList(123));
System.out.println(string);
System.out.println(integer);
}
}
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).
Java type inference (what's going on when you call static, generic methods like Array.asList) is complicated and not well-specified in the JLS. This paper from 2008 has a very interesting description of some of the issues and how it might be fixed:
Java Type Inference is Broken: How Can We Fix it?
Eclipse can produce byte code out of this:
public class Bla {
private static BigDecimal abc(List<BigDecimal> l) {
return l.iterator().next().multiply(new BigDecimal(123));
}
private static String abc(List<String> l) {
return l.iterator().next().length() + "";
}
public static void main(String[] args) {
System.out.println(abc(Arrays.asList("asdf")));
System.out.println(abc(Arrays.<BigDecimal>asList(new BigDecimal(123))));
}
}
Output:
4
15129
Looks like the compiler chooses the most specific method based on the generics.
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static Object f(List<?> 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));
}
}
Output:
strings
numbers
精彩评论