I have an interface in Java 6 that compiles correctly:
public interface IMultiMap<K, V> extends Map<K, Set<V>> {
public int valueSize();
public boolean put(K key, V value);
public void clear(Object key);
public boolean isEmpty(Object key);
}
But in Java 7, this interface doesn't compile. I get a compile error on boolean put(K, V)
that it has the same erasure as V put(K, V)
. The full error from the compiler:
error: name clash: put(K#1,V#1) in IMultiMap and put(K#2,V#2) in Map have the same erasure, yet neither overrides the other
public boolean put(K key, V value);
where K#1,V#1,K#2,V#2 are type-variables:
K#1 extends Object declared in interface IMultiMap
V#1 extends Object declared in interface IMultiMap
K#2 extends Object declared in interface Map
V#2 extends Object declared in interface Map
For the record, adding any kind of overriding doesn't work. I tried explicitly overriding Map.put
, but the error still comes up. Changing the return type of my put
is moot since this error is blocking that potential error from ever being reached, and if this error were fixed, then the two methods wouldn't have the same name/parameter signature anyway.
I think I might try some reflection on Java 6 and see what the actual parameter types end up being in Java 6's compiled bytecode. It's clear that both Java 7 methods are being erased to put(Object, Object)
. I'll post the reflection results here once I do that.
In the meantime, my temporary workaround will be just to rename put
to putSingle
, but is this new behavior correct? Did some part of the generics specifications change for Java 7 that makes the old Java 6 behavior wrong? Or is this a bug in the Java 7 compiler?
Thanks in advance.
EDIT: I ran the reflection code. Check out my answer below.
Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related types, respectively. Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
Non-generic class can't extend generic class except of those generic classes which have already pre defined types as their type parameters.
To use Java generics effectively, you must consider the following restrictions: Cannot Instantiate Generic Types with Primitive Types. Cannot Create Instances of Type Parameters. Cannot Declare Static Fields Whose Types are Type Parameters.
Generics also provide type safety (ensuring that an operation is being performed on the right type of data before executing that operation). Hierarchical classifications are allowed by Inheritance. Superclass is a class that is inherited. The subclass is a class that does inherit.
I think it's a bug in 1.6 that was fixed in 1.7. Extract from this page:
Synopsis: A Class Cannot Define Two Methods with the Same Erased Signature but Two Different Return Types
Description: A class cannot define two methods with the same erased signature, regardless of whether the return types are the same or not. This follows from the JLS, Java SE 7 Edition, section 8.4.8.3. The JDK 6 compiler allows methods with the same erased signature but different return types; this behavior is incorrect and has been fixed in JDK 7.
Example:
class A {
int m(List<String> ls) { return 0; }
long m(List<Integer> ls) { return 1; }
}
This code compiles under JDK 5.0 and JDK 6, and is rejected under JDK 7.
I ran the reflection code on Java 6.
Here's the code:
public static void main(String[] args) {
Class<IMultiMap> immClass = IMultiMap.class;
Method[] methods = immClass.getMethods();
for (Method method : methods) {
if (method.getName().equals("put"))
System.out.println(method.toString());
}
}
Here are the method signatures for the class:
public abstract boolean IMultiMap.put(java.lang.Object,java.lang.Object)
public abstract java.lang.Object java.util.Map.put(java.lang.Object,java.lang.Object)
Or more concisely:
boolean put(Object, Object)
Object put(Object, Object)
So they are erased to the same parameters with a different return type. I guess it's a bug an unspecified edge case in the Java 6 JLS then, as per assylias' answer. I wonder how Java 6 managed to resolve these methods correctly on runtime?
Edit: According to x4u's comment, the calling bytecode maintains a reference to the entire signature when it's compiled, so that's why the correct method was being called by the JVM. Since the compiler was probably capable of telling which method I was calling due to its access to the source (and thus to the generics information), the compiler probably linked it to the correct method via the entire signature. Interesting to know!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With