46

I'm trying to understand how java deals with ambiguities in function calls. In the following code, the call to method is ambiguous, but method2 is not!!!.

I feel both are ambiguous, but why does this compile when I comment out the call to method? Why is method2 not ambiguous as well?

public class A {
    public static <K> List<K> method(final K arg, final Object... otherArgs) {
        System.out.println("I'm in one");
        return new ArrayList<K>();
    }

    public static <K> List<K> method(final Object... otherArgs) {
        System.out.println("I'm in two");
        return new ArrayList<K>();
    }

    public static <K, V> Map<K, V> method2(final K k0, final V v0, final Object... keysAndValues) {
        System.out.println("I'm in one");
        return new HashMap<K,V> ();
    }

    public static <K, V> Map<K, V> method2(final Object... keysAndValues) {
        System.out.println("I'm in two");
        return new HashMap<K,V>();
    }

    public static void main(String[] args) {
        Map<String, Integer> c = A.method2( "ACD", new Integer(4), "DFAD" );
        //List<Integer> d = A.method(1, "2", 3  );
    }
}

EDIT: This came up in comments: A number of IDEs report both as ambiguous - IntelliJ and Netbeans so far. However, it compiles just fine from command-line/maven.

Gray
  • 115,027
  • 24
  • 293
  • 354
Chip
  • 3,226
  • 23
  • 31
  • Maybe it would increase compilation time if java checked for unused methods – Joelmob May 17 '12 at 19:33
  • @Joelmob I feel the question is why does `method2` compile when it seems to be just as ambiguous as `method`. – Jivings May 17 '12 at 19:35
  • @BrianRoach which java are you using ? I'm using 1.6 on MacOSX. Interestingly IntelliJ reports both as ambiguous, but it compiles form the commandline. – Chip May 17 '12 at 19:37
  • not sure indeed, but i think it has to do with you method 2 has 2 different types of arguments K and V. – Plínio Pantaleão May 17 '12 at 19:38
  • NM, Netbeans says both are ambiguous, but Java doesn't. Odd. – Brian Roach May 17 '12 at 19:38
  • I think the question should be: "Why is `method2` not ambiguous in this situation". – Jivings May 17 '12 at 19:38
  • @Jivings added explicit question on why `method2` is not ambiguous. – Chip May 17 '12 at 19:41
  • @esaj: With method or method2? The compile error is with method. – Jeremy May 17 '12 at 19:47
  • 13
    This is really going to hurt your head but, here's the rules from the spec: http://docs.oracle.com/javase/specs/jls/se5.0/html/expressions.html#15.12.2.5 - What I suspect is that with the two generic parameters `method2` can be said to be "strictly more specific" - I leave you to do the math there, I'm at lunch :-D – Brian Roach May 17 '12 at 19:47
  • @esaj Same Environment here (but Arch Linux) and I get a compilation error in Helios on `A.method`. – Jivings May 17 '12 at 19:47
  • @JeremyHeiler: just noticed that it was only calling method2, my bad =) – esaj May 17 '12 at 19:48
  • 4
    With `javac 1.7.0_02` it doesn't compile, even from command line, `method2` is properly considered ambiguous. – alain.janinm May 17 '12 at 19:54
  • 2
    Interesting that the compilation behavior in Java 7 is different. [The spec](http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.5) looks similar given a cursory glance. – Vivin Paliath May 17 '12 at 20:19
  • 4
    Thanks @alain.janinm I think there was a bug in JDK6. Some bugs related to ambiguous invocations have been solved in Java 7, according to http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#jdk7. I couldn't find this particular case. – Chip May 17 '12 at 20:27
  • 1
    Probably, moreover according to varargs doc `you should not overload a varargs method, or it will be difficult for programmers to figure out which overloading gets called.` However it's interesting to understand how works the resolution of the "most specific method". If you need some help to understand the spec you can read http://stackoverflow.com/q/6023439/1140748 and also a well explained example https://forums.oracle.com/forums/message.jspa?messageID=9485871#9485871. – alain.janinm May 17 '12 at 20:44
  • Is it even possible to call the second `method2` in this case? For example, `A.method2( "ACD", "ABC", "DFAD" );` still calls the first `method2` whereas `Map c = A.method2( "ACD", "ABC", "DFAD" );` results in a compile-time error. Seems to me that the first `method2` is the one that is considered, no matter what. – Vivin Paliath May 17 '12 at 21:09
  • @VivinPaliath: yes, when you pass only zero or one arguments. – dragon66 May 17 '12 at 21:14
  • @dragon66 Oops, I meant when passing in > 1 arguments. You're right with zero or one argument the second `method2` gets called. – Vivin Paliath May 17 '12 at 21:16
  • 1
    Following the authors of "SCJP Sun Certified Programmer for Java 6 (Exam 310-065)", we should not rely on IDE errors or warnings when in doubt but rather give credit for javac output command instead. Saying this i think that this is a duplicate of http://stackoverflow.com/questions/5361513/reference-is-ambiguous-with-generics that in turn resumes to a bug for oracle at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7031404 – Zecas May 18 '12 at 09:45
  • Related to http://stackoverflow.com/questions/951388/in-java-why-is-the-call-foo-not-ambigious-given-2-varags-methods-fooint-i – Gray May 22 '12 at 17:59
  • @Gary its not very similar :-) The question your refered (stackoverflow.com/questions/951388/) is according to spec i.e. explainable. This one appears to be a bug in JDK6 (fixed in JDK7?). – Chip May 22 '12 at 18:06

1 Answers1

14

An intuitive way to test whether method1 is more specific than method2 is to see whether method1 can be implemented by invoking method2 with the same parameters

method1(params1){
    method2(params1);   // if compiles, method1 is more specific than method2
}

If there are varargs, we may need to expand a vararg so that 2 methods have same number of params.

Let's check the first two method()s in your example

<K> void method_a(K arg, Object... otherArgs) {
    method_b(arg, otherArgs);   //ok L1
}
<K> void method_b(Object arg, Object... otherArgs) { // extract 1 arg from vararg
    method_a(arg, otherArgs);   //ok L2
}

(return types are not used in determining specificity, so they are omitted)

Both compile, therefore each is more specific than the other, hence the ambiguity. Same goes for your method2()s, they are more specific than each other. Therefore the call to method2() is ambiguous and shouldn't compile; otherwise it's a compiler bug.


So that's what the spec says; but is it proper? Certainly, method_a looks more specific than method_b. Actually if we have a concrete type instead of K

void method_a(Integer arg, Object... otherArgs) {
    method_b(arg, otherArgs);   // ok
}
void method_b(Object arg, Object... otherArgs) {
    method_a(arg, otherArgs);   // error
}

then only method_a is more specific than method_b, not vice versa.

The discrepancy arises from the magic of type inference. L1/L2 calls a generic method without explicit type arguments, so the compiler tries to infer the type arguments. The goal of type inference algorithm is to find type arguments such that the code compiles! No wonder L1 and L2 compile. L2 is actually infer to be this.<Object>method_a(arg, otherArgs)

Type inference tries to guess what the programmer wants, but the guess must be wrong sometimes. Our real intention is actually

<K> void method_a(K arg, Object... otherArgs) {
    this.<K>method_b(arg, otherArgs);   // ok
}
<K> void method_b(Object arg, Object... otherArgs) {
    this.<K>method_a(arg, otherArgs);   // error
}
Paul Grime
  • 14,970
  • 4
  • 36
  • 58
irreputable
  • 44,725
  • 9
  • 65
  • 93
  • Thank you for the intuitive test.. but it doesn't answer the question. The question is why does the invocation of method **fail** but method2 **passes**. – Chip May 18 '12 at 18:11
  • 2
    from the analysis, call to `method2` is ambiguous for lack of *most specific* method. therefore it is a compiler bug. – irreputable May 18 '12 at 18:39