432

Why is it not legal to have the following two methods in the same class?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

I get the compilation error

Method add(Set) has the same erasure add(Set) as another method in type Test.

while I can work around it, I was wondering why javac doesn't like this.

I can see that in many cases, the logic of those two methods would be very similar and could be replaced by a single

public void add(Set<?> set){}

method, but this is not always the case.

This is extra annoying if you want to have two constructors that takes those arguments because then you can't just change the name of one of the constructors.

Omry Yadan
  • 31,280
  • 18
  • 64
  • 87

8 Answers8

396

This rule is intended to avoid conflicts in legacy code that still uses raw types.

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 maintain compatibility with old code using raw types. It is not a limitation required by the erasure of type parameters; because method resolution occurs at compile-time, adding generic types to the method identifier would have been sufficient.

erickson
  • 265,237
  • 58
  • 395
  • 493
  • 3
    Great answer and example! I am not sure, however, if I fully understand your last sentence ("Because method resolution occurs at compile-time, before erasure, type reification is not required to make this work."). Could you elaborate a bit? – Jonas Eicher Dec 04 '12 at 13:56
  • 2
    Makes sense. I just spent some time thinking about type reification in template methods, but yeah: the compiler makes sure the right method gets selected before type erasure. Beautiful. If it weren't tainted by the legacy code compatibility issues. – Jonas Eicher Dec 05 '12 at 09:03
  • 1
    A couple years have passed since the last comment or edit here. Has anything changed, perhaps with Java 8? Tell me that there is a command-line option to `javac` which allows it to forsake old classes. That would make my day! – daveloyall Dec 04 '14 at 00:23
  • 1
    @daveloyall No, I am not aware of such an option for `javac`. – erickson Dec 04 '14 at 00:32
  • 24
    Not the first time I encounter Java error which is no error at all and could be compiled if only the authors of Java used warnings as everybody else does. Only they think they know everything better. – Tomáš Zato Apr 12 '15 at 22:50
  • By the way is there any workaround to the constructor problem? – Tomáš Zato Apr 12 '15 at 22:54
  • @TomášZato which constructor problem? – erickson Apr 12 '15 at 23:30
  • 1
    @erickson When your class has two constructors which accept two different lists, such as `List` and `List`. They operate on the lists differently but they can not exist separately due to this error. So I need a way to define both operations within one constructor that takes just `List`. I have no idea how to check for the correct generic types and cast given `List` to `List` as needed. – Tomáš Zato Apr 12 '15 at 23:36
  • 1
    @TomášZato No, I don't know of any clean workarounds for that. If you want something dirty, you could pass the `List>` and some `enum` that you define to indicate type. The type-specific logic could actually be in an method on the enum. Alternatively, you might want to create two different classes, rather than one class with two different constructors. Common logic would be in a superclass or a helper object to which both types delegate. – erickson Apr 13 '15 at 16:24
134

Java generics uses type erasure. The bit in the angle brackets (<Integer> and <String>) gets removed, so you'd end up with two methods that have an identical signature (the add(Set) you see in the error). That's not allowed because the runtime wouldn't know which to use for each case.

If Java ever gets reified generics, then you could do this, but that's probably unlikely now.

GaryF
  • 23,950
  • 10
  • 60
  • 73
  • 27
    I am sorry but your answer (and the other answers) do not explain why there is an error here. Overload resolution is done at compile time and the compiler surely has the type information needed to decide which method to link by address or by whatever the method is referenced in bytecode which I believe is not signature. I even think some compilers will allow this to compile. – Stilgar Jun 25 '10 at 12:32
  • 6
    @Stilgar what's to stop the method being called or inspected via reflection? The list of methods returned by Class.getMethods() would have two identical methods, which wouldn't make sense. – Adrian Mouat Mar 04 '11 at 10:42
  • 5
    The reflection information can/should contain the metadata needed to work with generics. If not how does the Java compiler know about generic methods when you import already compiled library? – Stilgar Mar 04 '11 at 12:22
  • Good question. I had to look it up. See http://tutorials.jenkov.com/java-reflection/generics.html Note that getMethod just takes the class of the generic e.g. Set. So getMethod would want to two return methods for the submitters code, which it can't. – Adrian Mouat Mar 04 '11 at 12:39
  • 4
    Then the getMethod method needs fixing. For example introduce an overload wich specifies generic overload and make the original method return non-generic version only, not returning any method anotated as generic. Of course this should have been done in version 1.5. If they do it now they will break backward compatibility of the method. I stand by my statement that type erasure does not dictate this behaviour. It is the implementation that did not get enough work probably due to limited resources. – Stilgar Mar 05 '11 at 14:56
  • For the most part I like Java generics, and this is the correct answer, of course, but I have to say this limitation really sucks. – Dave Jun 13 '12 at 14:21
  • @erickson - I would be interested to learn more on this then because that was not my understanding. Although if the information is present in the class file, but unused by the JVM, we are still stuck with this limitation, yes? – Dave Jun 29 '12 at 18:19
  • 1
    This is not a precise answer, but it does quickly sum up the issue in a useful fiction: the method signatures are too similar, the compiler might not tell the difference, and then you'll get "unresolved compilation problems." – worc Oct 08 '13 at 16:45
  • 1
    Actually the javac compiler from Java 1.6 did compile such methods (with the same erasure) correctly, as long as they had different return types - see http://stackoverflow.com/questions/5527235/type-erasure-and-overloading-in-java-why-does-this-work. Unfortunately they "fixed" it in Java 7... :( – DisplayName Jun 18 '14 at 01:38
51

This is because Java Generics are implemented with Type Erasure.

Your methods would be translated, at compile time, to something like:

Method resolution occurs at compile time and doesn't consider type parameters. (see erickson's answer)

void add(Set ii);
void add(Set ss);

Both methods have the same signature without the type parameters, hence the error.

Community
  • 1
  • 1
bruno conde
  • 47,767
  • 15
  • 98
  • 117
28

The problem is that Set<Integer> and Set<String> are actually treated as a Set from the JVM. Selecting a type for the Set (String or Integer in your case) is only syntactic sugar used by the compiler. The JVM can't distinguish between Set<String> and Set<Integer>.

kgiannakakis
  • 103,016
  • 27
  • 158
  • 194
  • 7
    It's true that the JVM *runtime* has no information to distinguish each `Set`, but since the method resolution happens at compile-time, when the necessary information is available, this isn't relevant. The problem is that allowing these overloads would conflict with the allowance for raw types, so they were made illegal in the Java syntax. – erickson Dec 11 '11 at 21:21
  • @erickson Even when the compiler knows what method to call, it can't as in the bytecode, they both look exactly the same. You'd need to change how a method call gets specified, as `(Ljava/util/Collection;)Ljava/util/List;` doesn't work. You could use `(Ljava/util/Collection;)Ljava/util/List;`, but that's an incompatible change and you'd run into unsolvable problems in places where all you have is an erased type. You'd probably have to drop erasure completely, but that's pretty complicated. – maaartinus Sep 18 '18 at 15:26
  • @maaartinus Yes, I agree that you'd have to change the method specifier. I'm trying to get at some of the unsolvable problems that led them to give up on that attempt. – erickson Sep 18 '18 at 16:44
8

Define a single Method without type like void add(Set ii){}

You can mention the type while calling the method based on your choice. It will work for any type of set.

Idrees Ashraf
  • 1,363
  • 21
  • 38
3

It could be possible that the compiler translates Set(Integer) to Set(Object) in java byte code. If this is the case, Set(Integer) would be used only at compile phase for syntax checking.

rossoft
  • 2,182
  • 17
  • 17
  • 6
    It's technically just the raw type `Set`. Generics don't exist in byte code, they're syntactic sugar for casting and provide compile-time type safety. – Andrzej Doyle Jan 04 '10 at 13:40
3

I bumped into this when tried to write something like: Continuable<T> callAsync(Callable<T> code) {....} and Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} They become for compiler the 2 definitions of Continuable<> callAsync(Callable<> veryAsyncCode) {...}

The type erasure literally means erasing of type arguments information from generics. This is VERY annoying, but this is a limitation that will be with Java for while. For constructors case not much can be done, 2 new subclasses specialized with different parameters in constructor for example. Or use initialization methods instead... (virtual constructors?) with different names...

for similar operation methods renaming would help, like

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

Or with some more descriptive names, self-documenting for oyu cases, like addNames and addIndexes or such.

Kote Isaev
  • 273
  • 4
  • 13
0

In this case can use this structure:

class Test{
   void add(Integer ... ii){}
   void add(String ... ss){}
}

and inside methods can create target collections

void add(Integer ... values){
   this.values = Arrays.asList(values);
}
Piotr Rogowski
  • 3,642
  • 19
  • 24