Reputation: 7695
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>
andUnion<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
andgetA
stuffprotected
, 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)
andpublic static Union<A, B> fromB(B b)
) is the best approach (along with making the real constructor private).Union<A, B>
andUnion<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.
Upvotes: 10
Views: 875
Reputation: 3200
If you must use a constructor then most likely there is not an elegant solution.
With factory methods you can have an elegant solution which preserves final for a and b.
The factory methods will use the "ugly" constructor but that is ok since it is part of the implementation. The public interface keeps all your requirements save for moving from constructors to factory methods.
This is made with the intention of making Union<A,B>
interchangeable with Union<B,A>
as per durron597's answer.
That is not entirelly possible as I'll show later with an example, but we can get quite close.
public class Union<A, B> {
private final A a;
private final B b;
private Union(A a, B b) {
assert a == null ^ b == null;
this.a = a;
this.b = b;
}
public static <A, B> Union<A, B> valueOfA(A a) {
if (a == null) {
throw new IllegalArgumentException();
}
Union<A, B> res = new Union<>(a, null);
return res;
}
public static <A, B> Union<A, B> valueOfB(B b) {
if (b == null) {
throw new IllegalArgumentException();
}
Union<A, B> res = new Union<>(null, b);
return res;
}
public boolean isClass(Class<?> clazz) {
return a != null ? clazz.isInstance(a) : clazz.isInstance(b);
}
// The casts are always type safe.
@SuppressWarnings("unchecked")
public <C> C get(Class<C> clazz) {
if (a != null && clazz.isInstance(a)) {
return (C)a;
}
if (b != null && clazz.isInstance(b)) {
return (C)b;
}
throw new IllegalStateException("This Union does not contain an object of class " + clazz);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Union)) {
return false;
}
Union union = (Union) o;
Object parm = union.a != null ? union.a : union.b;
return a != null ? a.equals(parm) : b.equals(parm);
}
@Override
public int hashCode() {
int hash = 3;
hash = 71 * hash + Objects.hashCode(this.a);
hash = 71 * hash + Objects.hashCode(this.b);
return hash;
}
}
Here are examples of how to use and how not to use it.
useUnionAsParm2
shows the limitation of this solution. The compiler cannot detect a wrong parameter used for a method which is meant to accept any Union containing a String. We have to resort to runtime type checking.
public class Test {
public static void main(String[] args) {
Union<String, Integer> alfa = Union.valueOfA("Hello");
Union<Integer, String> beta = Union.valueOfB("Hello");
Union<HashMap, String> gamma = Union.valueOfB("Hello");
Union<HashMap, Integer> delta = Union.valueOfB( 13 );
// Union<A,B> compared do Union<B,A>.
// Prints true because both unions contain equal objects
System.out.println(alfa.equals(beta));
// Prints false since "Hello" is not an Union.
System.out.println(alfa.equals("Hello"));
// Union<A,B> compared do Union<C,A>.
// Prints true because both unions contain equal objects
System.out.println(alfa.equals(gamma));
// Union<A,B> compared to Union<C,D>
// Could print true if a type of one union inherited or implement a
//type of the other union. In this case contained objects are not equal, so false.
System.out.println(alfa.equals(delta));
useUnionAsParm(alfa);
// Next two lines produce compiler error
//useUnionAsParm(beta);
//useUnionAsParm(gamma);
useUnionAsParm2(alfa);
useUnionAsParm2(beta);
useUnionAsParm2(gamma);
// Will throw IllegalStateException
// Would be nice if it was possible to declare useUnionAsParm2 in a way
//that caused the compiler to generate an error for this line.
useUnionAsParm2(delta);
}
/**
* Prints a string contained in an Union.
*
* This is an example of how not to do it.
*
* @param parm Union containing a String
*/
public static void useUnionAsParm(Union<String, Integer> parm) {
System.out.println(parm.get(String.class));
}
/**
* Prints a string contained in an Union. Correct example.
*
* @param parm Union containing a String
*/
public static void useUnionAsParm2(Union<? extends Object, ? extends Object> parm) {
System.out.println( parm.get(String.class) );
}
}
Upvotes: 2
Reputation: 7695
As durron597's answer points out, Union<Foo, Bar>
and Union<Bar, Foo>
should, conceptually speaking, behave the same but behave very differently. It doesn't matter which is A
and which is B
, it matter which is which type.
Here's what I think may be best,
// I include this because if it's not off in its own package away from where its
// used these protected methods can still be called. Also I specifically use an
// abstract class so I can make the methods protected so no one can call them.
package com.company.utils;
public abstract class Union<A, B> {
private A a;
private B b;
protected Union(A a, B b) {
assert a == null ^ b == null: "Exactly one param must be null";
this.a = a;
this.b = b;
}
// final methods to prevent over riding in the child and calling them there
protected final boolean isA() { return a != null; }
protected final boolean isB() { return b != null; }
protected final A getA() {
if (!isA()) { throw new IllegalStateException(); }
return a;
}
protected final B getB() {
if (!isB()) { throw new IllegalStateException(); }
return b;
}
}
And the implementation. Where this is used (except for com.company.utils
) only the methods with unambiguous names can be found.
package com.company.domain;
import com.company.utils.Union;
public class FooOrBar extends Union<Foo, Bar> {
public FooOrBar(Foo foo) { super(foo, null); }
public FooOrBar(Bar bar) { super(null, bar); }
public boolean isFoo() { return isA(); }
public boolean isBar() { return isB(); }
public Foo getFoo() { return getA(); }
public Bar getBar() { return getB(); }
}
Another idea may be a Map<Class<?>, ?>
or something, or at least to hold the values. I don't know. All this code stinks. It was birthed from a poorly designed method that needed multiple return types.
Upvotes: 0
Reputation: 32323
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.
Upvotes: 5
Reputation: 19672
"Union" is the wrong word here. We are not talking about the union of two types, which will include all objects in either types, possibly with overlapping.
This data structure is more like a tuple, with an extra index pointing to one significant element. A better word for it is probably "option". In fact, java.util.Optional
is a special case of it.
So, I might design it like this
interface Opt2<T0,T1>
int ordinal(); // 0 or 1
Object value();
default boolean is0(){ return ordinal()==0; }
default T0 get0(){ if(is0()) return (T0)value(); else throw ... }
static <T0,T1> Opt2<T0,T1> of0(T0 value){ ... }
Upvotes: 1
Reputation: 148870
I would use a private constructor and 2 static creators
public class Union<A, B> {
private final A a;
private final B b;
// private constructor to force use or creation methods
private Union(A a, B b) {
if ((a == null) && (b == null)) { // ensure both cannot be null
throw new IllegalArgumentException();
}
this.a = a;
this.b = b;
}
public static <A, B> Union<A, B> unionFromA(A a) {
Union<A,B> u = new Union(a, null);
return u;
}
public static <A, B> Union<A, B> unionFromB(B b) {
Union<A,B> u = new Union(null, b);
return u;
}
...
}
Upvotes: 2