7

I'm trying to migrate Java 7 code to Java 8, so I've code similar to:

package tests;

import java.util.Arrays;
import java.util.Map;

public class Tests {
    private static interface ComparableMap<K,V> extends Map<K,V>, Comparable {}

    public static void main(String[] args) {
        func(getString());
    }

    private static void func(Comparable...input){
        System.out.println(Arrays.toString(input));
    }

    private static void func(ComparableMap <?,?> m){
        System.out.println(m);
    }

    private static <T extends Comparable> T getString(){
        return (T) "aaa";
    }
}

In java 7 it working properly, in java 8 I'm getting:

java.lang.ClassCastException: java.lang.String cannot be cast to tests.Tests$ComparableMap

And in case I'm changing one function definition to:

    private static <T> T getString(){
        return (T) "aaa";
    }

compilation will fail with: error:

reference to func is ambiguous

Why Java 8 compiler not failing in first case? (Looks bug to me) Is it possible to change 2nd overloaded function, in order to get called first function with varargs argument without changing call itself?

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • Possible duplicate of [Java type inference: reference is ambiguous in Java 8, but not Java 7](http://stackoverflow.com/questions/28466925/java-type-inference-reference-is-ambiguous-in-java-8-but-not-java-7) – Andrew Tobilko Nov 20 '16 at 11:26
  • 6
    @AndrewTobilko I don't think it's a correct duplicate, since here the OP is getting a runtime error in Java 8, and in the possible duplicate it's a compilation error in Java 8. – Eran Nov 20 '16 at 11:29
  • 3
    Use of raw-types + methods declared to return a generic `T` the caller wants + overloaded method and varargs; quite the explosive set-up. [This question](http://stackoverflow.com/questions/28466925/java-type-inference-reference-is-ambiguous-in-java-8-but-not-java-7?noredirect=1&lq=1) explains the error in Java 8 though, added with the fact that `func(ComparableMap)` is more specific than `func(Comparable...)`. – Tunaki Nov 20 '16 at 15:15
  • 1
    [Here](http://stackoverflow.com/q/28692384/2711488) is an example of similar code, failing at runtime with a `ClassCastException`. The problem is the same. As explained [here](http://stackoverflow.com/a/36403072/2711488), the inferred type can be hypothetical in that it has to extend both interfaces and it doesn’t matter whether such an actual implementation really exists. The fix is easy: change `getString()` to `private static String getString(){ return "aaa"; }`. – Holger Nov 21 '16 at 12:48

2 Answers2

6

The compiler error

In the first case, the method getString is required to return a Comparable instance. The compiler looks for overloads of the func method, and it finds only one that can accept a Comparable: func(Comparable ... input). Map doesn't implement that interface, so the second overload is not applicable. There is no ambiguity.

In the second case, getString can return anything. Both overloads work, so there is an ambiguity. Note though, the cast to T is unsafe/wrong in both cases.

The usafe cast

The kind of generic method you've written basically tells the compiler "I can return an instance of any class you want that implements Comparable". You can't actually keep this promise though.

Lets say I have the following code:

String str = getString();
Integer num = getString();

This code will compile, both String and Integer implement the Comparable interface. The second line will fail at runtime though: the code tries to cast a String to an Integer.

Your second case is also wrong for the same reason I explained above. It promises it can return any type you want. It obiously can't keep that promise too (Runnable here is a random example, it could be anything):

Runnable run = getString()

Your updated code

The compiler sees two possible overloads that both match, func(Comparable...input) and func(ComparableMap <?,?> m). It prefers the second one, because varargs methods are always chosen last (for compatebility reasons). All of this is corect behaviour.

The code then throws the ClassCastException because your getString method doesn't keep it promise (letting the caller decide what kind of Comparable to return).

How to fix it?

The fundamental problem is that your getString method makes a false promise. As such, I don't really know what that code tries to accomplish. If you can elaborate we may be able to help you furter.

Community
  • 1
  • 1
Todd Sewell
  • 1,444
  • 12
  • 26
  • see updated code, now the Map also Comparable, still getting the same. I'm trying to accomplish one of following: get compiler error or get code working like in Java 7. – Alexander Slepoy Nov 20 '16 at 12:46
  • 3
    Okay, let me think a bit about your updated code. My point is that that Java 7 code was wrong, there is no way to get it "working like in Java 7". The compiler became a bit smarter in Java 8, so it simply behaves different on wrong code like this now. – Todd Sewell Nov 20 '16 at 12:51
  • 2
    I added an section that explains why your updated code also fails. – Todd Sewell Nov 20 '16 at 13:24
  • @AlexanderSlepoy If you feel like my answer has helped you, you might want to click the green checkmark next to this answer to indicate that your problem was solved. – Todd Sewell Nov 25 '16 at 23:25
-1

Yeah, I have no idea why Java 8 is choosing the overload that takes a map over the one that takes the Comparable vararg. I'm going to guess that good old type erasure is at play here.

That said, I would just make getString() return a Comparable rather than a T.

Joe C
  • 15,324
  • 8
  • 38
  • 50