Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling type erasure in constructors with generics

I am trying to make a class that can hold only one of two objects and I want to do this with generics. Here is the idea:

public class Union<A, B> {

    private final A a;    
    private final B b;

    public Union(A a) {
        this.a = a;
        b = null;
    }

    public Union(B b) {
        a = null;
        this.b = b;
    }

    // isA, isB, getA, getB...

}

Of course this won't work though because due to type erasure the constructors have the same type signature. I realize one solution is to have a single constructor taking both, but I want one of the values to be empty, so it seems more elegant to have single parameter constructors.

// Ugly solution
public Union(A a, B b) {
    if (!(a == null ^ b == null)) {
        throw new IllegalArgumentException("One must exist, one must be null!");
    }
    this.a = a;
    this.b = b;
}

Is there an elegant solution to this?


Edit 1: I am using Java 6.

Edit 2: The reason I want to make this is because I have a method that can return one of two types. I made a concrete version with no generics but was wondering if I could make it generic. Yes, I realize that having a method with two different return types is the real issue at hand, but I was still curious if there was a good way to do this.

I think durron597's answer is best because it points out that Union<Foo, Bar> and Union<Bar, Foo> should act the same but they don't (which is the main reason why I decided to stop pursuing this). This is a much uglier issue than the "ugly" constructor.

For what it's worth I think the best option is probably make this abstract (because interfaces can't dictate visibility) and make the isA and getA stuff protected, and then in the implementing class have better named methods to avoid the <A, B> != <B, A> issue. I will add my own answer with more details.

Final edit: For what it's worth, I decided that using static methods as pseudo constructors (public static Union<A, B> fromA(A a) and public static Union<A, B> fromB(B b)) is the best approach (along with making the real constructor private). Union<A, B> and Union<B, A> would never realistically be compared to each other when it's just being used as a return value.

Another edit, 6 months out: I really can't believe how naive I was when I asked this, static factory methods are so obviously the absolute correct choice and clearly a no-brainer.

All that aside I have found Functional Java to be very intriguing. I haven't used it yet but I did find this Either when googling 'java disjunct union', it's exactly what I was looking for. The downside though it that Functional Java is only for Java 7 and 8, but luckily the project I am now working on used Java 8.

like image 572
Captain Man Avatar asked Aug 07 '15 14:08

Captain Man


1 Answers

It doesn't really make any sense to do this. When would this ever make sense? For example (assuming, for the moment, your initial code worked):

Union<String, Integer> union = new Union("Hello");
// stuff
if (union.isA()) { ...

But if you did, instead:

Union<Integer, String> union = new Union("Hello");
// stuff
if (union.isA()) { ...

This would have different behavior, even though the classes and the data are the same. Your concept of isA and isB are basically "left vs right" - it's more important which one is left vs right than which one is a String vs which one is an Integer. In other words, Union<String, Integer> is very different from Union<Integer, String>, which is probably not what you want.

Consider what would happen if we, for example, had, say:

List<Union<?, ?>> myList;
for(Union<?, ?> element : myList) {
  if(element.isA()) {
    // What does this even mean? 

The fact of something being an A doesn't matter, unless you care about whether it's a Left or a Right, in which case you should call it that.


If this discussion is not about left vs right, then the only thing that matters is using your specific types when creating the class. It would make more sense to simply have an interface;

public interface Union<A, B> {
  boolean isA();
  boolean isB();
  A getA();
  B getB();
}

You could even do the "is" method in an abstract class:

public abstract class AbstractUnion<A, B> {
  public boolean isA() { return getB() == null; }
  public boolean isB() { return getA() == null; }
}

And then, when you actually instantiate the class, you will use specific types anyway...

public UnionImpl extends AbstractUnion<String, Integer> {
  private String strValue;
  private int intValue

  public UnionImpl(String str) {
    this.strValue = str;
    this.intValue = null;
  }

  // etc.
}

Then, when you've actually chosen your implementation types, you'll actually know what you're getting.


Aside: if, after reading all of the above, you still want to do this the way you describe in your initial question, the right way to do it is with static factory methods with a private constructor as described @JoseAntoniaDuraOlmos's answer here. However, I hope you think further about what you actually need your class to do in a real use case.

like image 135
durron597 Avatar answered Oct 03 '22 22:10

durron597