6

Possible Duplicate:
Method has the same erasure as another method in type

In one of my classes I wanted to define these two methods:

private void add(List<ChangeSet> changeSetList) {
    for (ChangeSet changeSet : changeSetList) {
        add(changeSet);
    }
}

private void add(List<Change> changeList) {
    for (Change change : changeList) {
        add(change);
    }
}

Then I get the following error:

Method add(List<Change>) has the same erasure add(List<E>) as another method in type DataRetriever

Why isn´t this allowed? What is the problem with method definitions like that? And what should I do to avoid it? I don´t want to rename one of the methods.

Community
  • 1
  • 1

5 Answers5

12

That's just how the type system of Java "works". The generics List<Change> and List<ChangeSet> aren't actually different types. The generic parameters are just hints for the compiler to perform certain checks and certain casts. As far as the JVM and the type system is concerned, though, both types are actually "erased" to List<Object> (or just List if you will), and the two types are really the same, with no internal differences. Therefore, you cannot actually overload on different generics parameters, since as far as overload resolution is concerned, the two types are identical.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
7

This limitation is part of the language syntax, not the Java runtime itself. Essentially, this rule is intended to avoid conflicts in legacy code that still uses raw types.

A compiler like javac will reject this type of overloading, but if you create a class through other means (writing your own compiler, or using a byte-code engineering library like ASM) with signatures that differ only by type parameters, the javac compiler will resolve calls the correct method in your class.

Here's an illustration of why this was not allowed, drawn from the JLS. Suppose, before generics were introduced to Java, I wrote some code like this:

class CollectionConverter {
  List toList(Collection c) {...}
}

You extend my class, like this:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

After the introduction of generics, I decided to update my library.

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

You aren't ready to make any updates, so you leave your Overrider class alone. In order to correctly override the toList() method, the language designers decided that a raw type was "override-equivalent" to any generified type. This means that although your method signature is no longer formally equal to my superclass' signature, your method still overrides.

Now, time passes and you decide you are ready to update your class. But you screw up a little, and instead of editing the existing, raw toList() method, you add a new method like this:

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

Because of the override equivalence of raw types, both methods are in a valid form to override the toList(Collection<T>) method. But of course, the compiler needs to resolve a single method. To eliminate this ambiguity, classes are not allowed to have multiple methods that are override-equivalent—that is, multiple methods with the same parameter types after erasure.

The key is that this is a language rule designed to permit continued use of raw types, not a limitation arising from the erasure of type parameters.

If you eliminate legacy code (for example, by using your own, not-strictly-Java language), this type of overload functions perfectly. Because method resolution occurs at compile-time, before erasure, type reification is not required to make this work.

reevesy
  • 3,452
  • 1
  • 26
  • 23
erickson
  • 265,237
  • 58
  • 395
  • 493
  • This is interesting (after all, overload resolution is performed by the compiler), so I wonder if the Java language specification has anything to say on this matter. Would it be conforming of a compiler to do this, or does the spec actually require the two parametrised types to be treated as the same? – Kerrek SB Dec 11 '11 at 20:43
  • @KerrekSB It has a lot to say on the matter. Check out [JLS § 8.4.2](http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.2) – erickson Dec 11 '11 at 21:03
2

in addition to the answers by adarshr and Kerrek, why not just make it generic like below:

private <T> void add(List<T> changeList) {
    for (T change : changeList) {
        add(change);
    }
}

that should work for both cases...

aishwarya
  • 1,970
  • 1
  • 14
  • 22
1

Because generics are only a compile time aid to you. After compilation, there will be no generics related information stored in the bytecode.

Take a look at this:

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

adarshr
  • 61,315
  • 23
  • 138
  • 167
  • This is wrong. Types are only erased from the generic type itself; classes that use generic types (as fields, return or parameter types, supertypes, etc.) are full of generic type information, and can be inspected at runtime through the Reflection API. – erickson Dec 11 '11 at 20:36
0

After type-erasure both methods will have a signature of private void add(List), which isn't allowed.

You need to either rename the methods or pass another argument like the class of the list values.

rsp
  • 23,135
  • 6
  • 55
  • 69