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?
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 aComparable
rather than aT
.The compiler error
In the first case, the method
getString
is required to return aComparable
instance. The compiler looks for overloads of thefunc
method, and it finds only one that can accept aComparable
: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:
This code will compile, both
String
andInteger
implement theComparable
interface. The second line will fail at runtime though: the code tries to cast aString
to anInteger
.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):Your updated code
The compiler sees two possible overloads that both match,
func(Comparable...input)
andfunc(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 ofComparable
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.