7

I have the following generic class:

class Or<A,B>
{
  Or (A a) {}
  Or (B b) {}
}

Why do I get the following error when I try to compile it:

Or(A) is already defined in Or
    Or (B b)
    ^

It seems to me that the two constructors share the same signature although they have different generic type arguments. Why? And how to work around this problem?

Update

I understood the problem now. The compiler needs a way to distinguish the two types. Adding such a constrain would be ok for my use case. So I would like to add another question:

How to specify that the two types A and B may be anything but different?

ceving
  • 21,900
  • 13
  • 104
  • 178
  • 2
    Legacy [*type erasure*](http://en.wikipedia.org/wiki/Generics_in_Java#Problems_with_type_erasure). – Paul Jul 03 '13 at 10:14
  • @johnchen902, can you add this comment as answer? It would be great – Prasad Kharkar Jul 03 '13 at 10:16
  • @PrasadKharkar Because this question obviously a duplicate. I don't want to be the first answerer before I see someone 10k or more answer. – johnchen902 Jul 03 '13 at 10:20
  • You can't have a union of types, see: http://stackoverflow.com/questions/6592832/java-generics-parameter-bounding-to-any-of-a-range-of-types – David Roussel Jul 03 '13 at 12:11
  • Keep in mind that Java generics are a compiler fiction. There is essentially nothing in the compiled code that "remembers" they're generic. – Hot Licks Jul 03 '13 at 12:20
  • @DavidRoussel As the answer to the linked question states you can have a union by using a common interface. However, what the OP wants is a disjunction. And this is indeed not possible to be defined explicitly. – joe776 Jul 03 '13 at 13:28

9 Answers9

9

It seems to me that the two constructors share the same signature although they have different generic type arguments.

They do. The signature is

Or(Object o);

Why?

Because of type erasure implementation of generics in Java: references to generic types are converted to System.Object in all contexts where they are used; the generic type is known only to the compiler.

And how to work around this problem?

Unfortunately, you cannot easily work around this problem in a constructor. You can replace the overloaded constructors with factory methods, and give different names, say OrWithA and OrWithB:

// Hide the constructor
private Or(...) {
    ...
}
// Publish factory methods
public static <X> Or OrWithA(X a) {
    return new Or(...);
}
public static <X> Or OrWithB(X a) {
    return new Or(...);
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Does not work, because `non-static class A cannot be referenced from a static context`. – ceving Jul 03 '13 at 11:24
  • @ceving Thanks, I forgot about some limitations of Java generics after several years of working with C#. You can make your factory methods generic on some other type (see the edit), although it is certainly not as elegant as if you could use `A` and `B`. – Sergey Kalinichenko Jul 03 '13 at 13:10
2

They just do. That's the nature of generics; they provide syntatic sugar used only at compile time. There is no way round it.

(Acknowledge question comments) This is called type erasure: see http://en.wikipedia.org/wiki/Type_erasure

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
2

This is because A or B can be anything, they can be same also as generics is just for compile time. At runtime, they are lost due to type erasure

Prasad Kharkar
  • 13,410
  • 5
  • 37
  • 56
2

This is due to type erasure. The Eclipse compiler gives a more detailed error: Method Or(A) has the same erasure Or(Object) as another method in type Or

If you apply restrictions to the generics it compiles just fine:

class Or<A extends String, B extends Integer>
{
    Or(A a) {}

    Or(B b) {}
}
joe776
  • 1,106
  • 14
  • 23
  • I would need the constrain: A extends anything but the class of B. The types should be distinct. How to specify this? – ceving Jul 03 '13 at 11:00
  • @ceving It's not possible to specify something as general as "anything except B". You need two concrete classes or interfaces that are distinct. – joe776 Jul 03 '13 at 13:06
1

To say it differently: You have 2 types, A and B, and nothing is known about both of them. So one completely unknown type is as good as another one. How should the constructor calls be dispatched?

Ingo
  • 36,037
  • 5
  • 53
  • 100
0

This is not legal, since generics are discarded at runtime (this is type erasure). Both of your methods would have a prototype of Or(Object).

The only solution would be to have a OrA() and OrB() method -- or review your class entirely.

fge
  • 119,121
  • 33
  • 254
  • 329
0

That's because of a type erasure: generics are replaced with Object type on compilation, so your methods do have the same signature. As a workaound you may choose to narrow the types of A and B:

public class Test<A extends String, B extends Number> {

public Test(A arg){

}

public Test(B arg){

}
}
Jk1
  • 11,233
  • 9
  • 54
  • 64
0

These constructors will be seen as having the same signatures since their types cannot be identified at runtime.

Try specifying a bounds on at least one of the type parameters if possible.

class Or<A extends Number,B>
{
  Or (A a) {}
  Or (B b) {}
}
Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
0
  1. Consider this, which constructor to call?

    Or<Integer,Integer> o = new Or<>(5);
    
  2. Your problem really comes from Type erasure, it make your code look like this after compilation:

    class Or
    {
        Or (Object a) {}
        Or (Object b) {}
    }
    
johnchen902
  • 9,531
  • 1
  • 27
  • 69
  • How to specify that the types may be anything but distinct? That would be fine for my use case and should be sufficient for the compiler. – ceving Jul 03 '13 at 11:04
  • @ceving No, you cannot, I'm afraid. However maybe you can use [factory method](http://stackoverflow.com/a/929273/2040040) – johnchen902 Jul 03 '13 at 11:10