Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic class compiles in Java 6, but not Java 7

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.

like image 799
Brian Avatar asked Aug 30 '12 22:08

Brian


People also ask

What is the difference between generic class and generic method in Java?

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.

Can a generic class be a subclass of a non generic class?

Non-generic class can't extend generic class except of those generic classes which have already pre defined types as their type parameters.

What is not allowed for generics Java?

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.

Can we inherit generic class in Java?

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.


2 Answers

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.

like image 77
assylias Avatar answered Oct 01 '22 20:10

assylias


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!

like image 41
Brian Avatar answered Oct 01 '22 20:10

Brian