When you write code sometimes you want your object at some point contained the value of the same type or value of another type. Programming languages that support the concept of unions, allow at some point to save the current value in one memory region.
For example, in languages C/C++ you can write here.
union value { int i; float f;
}; union value v; v.i = 5; /* v.f - undefined behaivor */
Moreover, if we set the value of the same field to read the value of another field will will have indeterminate behavior.
To simplify the work with the union tipami in C++17 was added class std::variant.
std::variant<int, float> v { 5 }; std::cout << "int value:" << std::get<int>(v) << std::endl;
The Java language does not support union types. As an alternative, you can implement a date class with two fields of specific types with setters and getters. But I want the value persisted in a single field, not two.
As you know the Object type you can store a value of one type and then reassign the value of another type. And it can be used to implement a class similar to the class std::variant.
Because in Java you cannot specify a variable number of types in the generic, for a certain number of types need a specialization of this class(Union2, Union3, etc.). Write the main class Union base and its operations.
public abstract class Union { private Union() {} public abstract <T> void set(T value); public abstract <T> T get(Class<T> clazz); public abstract <T> boolean isActive(Class<T> clazz); public abstract <T> Class<T> getActive(); }
To create objects of the class will use the factory methods. Depending on the number of types will return as a particular specialization of the class.
public static <T1, T2> Union2<T1, T2> of(Class<T1> firstClass, Class<T2> secondClass) { return new Union2<>(firstClass, secondClass);
} public static <T1, T2, T3> Union3<T1, T2, T3> of(Class<T1> firstClass, Class<T2> secondClass, Class<T3> thirdClass) { return new Union3<>(firstClass, secondClass, thirdClass);
}
Specific specialization of a union of the class will keep a certain number of types and one field Object. If we specify not the correct type, you’ll get an error.
private static class Union2<T1, T2> extends Union { private final Class<T1> firstClass; private final Class<T2> secondClass; private Object value; private Union2(Class<T1> firstClass, Class<T2> secondClass) { this.firstClass = firstClass; this.secondClass = secondClass;
} @Override public <T> void set(T value) { if (value.getClass() == firstClass || value.getClass() == secondClass) { this.value = value; } else { throw new UnionException("Incorrect type:" + value.getClass().getName() + "\n" + "Union two types: [" + firstClass.getName() + ", " + secondClass.getName() + "]");
}
} @Override public <T> T get(Class<T> clazz) { if (clazz == firstClass || clazz == secondClass) { return (T) value; } else { throw new UnionException("Incorrect type:" + value.getClass().getName() + "\n" + "Union two types: [" + firstClass.getName() + ", " + secondClass.getName() + "]");
}
} @Override public <T> boolean isActive(Class<T> clazz) { return value.getClass() == clazz;
} @Override public <T> Class<T> getActive() { return (Class<T>) value.getClass();
}
} private static class Union3<T1, T2, T3> extends Union { private final Class<T1> firstClass; private final Class<T2> secondClass; private final Class<T3> thirdClass; private Object value; private Union3(Class<T1> firstClass, Class<T2> secondClass, Class<T3> thirdClass) { this.firstClass = firstClass; this.secondClass = secondClass; this.thirdClass = thirdClass;
} @Override public <T> void set(T value) { if (value.getClass() == firstClass || value.getClass() == secondClass || value.getClass() == thirdClass) { this.value = value; } else { throw new UnionException("Incorrect type:" + value.getClass().getName() + "\n" + "Union of three types: [" + firstClass.getName() + ", " + secondClass.getName() + ", " + thirdClass.getName() + "]");
}
} @Override public <T> T get(Class<T> clazz) { if (clazz == firstClass || clazz == secondClass || value.getClass() == thirdClass) { return (T) value; } else { throw new UnionException("Incorrect type:" + value.getClass().getName() + "\n" + "Union of three types: [" + firstClass.getName() + ", " + secondClass.getName() + ", " + thirdClass.getName() + "]");
}
} @Override public <T> boolean isActive(Class<T> clazz) { return value.getClass() == clazz;
} @Override public <T> Class<T> getActive() { return (Class<T>) value.getClass();
}
}
</> Now let's look at an example of how to use this class. As you can see not working with a specific specialization Union, making the code simpler. <source lang="java"> TriUnion Union = Union.of(Integer.class, String.class, Float.class);
triUnion.set(15f); assertEquals(triUnion.getActive(), Float.class);
assertTrue(triUnion.isActive(Float.class)); triUnion.set("Dot"); assertEquals(triUnion.getActive(), String.class);
assertTrue(triUnion.isActive(String.class)); triUnion.set(10); assertEquals(triUnion.getActive(), Integer.class);
assertTrue(triUnion.isActive(Integer.class));
Also to check the current value, you can write a simple visitor.
BiUnion Union = Union.of(Integer.class, String.class);
biUnion.set("Line"); TriUnion Union = Union.of(Integer.class, String.class, Float.class);
triUnion.set(15f); matches(biUnion, Integer.class i -> System.out.println("bi-union number:" + i), String.class s -> System.out.println("bi-union string:" + s)
); matches(triUnion, Integer.class i -> System.out.println("tri-union int:" + i), String.class s -> System.out.println("tri-union string:" + s); Float.class, f -> System.out.println("tri-union float:" + f)
);
public static <V, T1, T2> void matches(V value, Class<T1> firstClazz, Consumer<T1> firstConsumer, Class<T2> secondClazz, Consumer<T2> secondConsumer) { Class<?> valueClass = value.getClass(); if (firstClazz == valueClass) { firstConsumer.accept((T1) value); } else if (secondClazz == valueClass) { secondConsumer.accept((T2) value);
}
} public static <T1, T2, T3> void matches(Union value Class<T1> firstClazz, Purchaser<T1> firstConsumer, Class<T2> secondClazz, Purchaser<T2> secondConsumer, Class<T3> thirdClazz, Purchaser<T3> thirdConsumer) { Class<?> valueClass = value.getActive(); if (firstClazz == valueClass) {
firstConsumer.obtain(value.get(firstClazz)); } else if (secondClazz == valueClass) {
secondConsumer.obtain(value.get(secondClazz)); } else if (thirdClazz == valueClass) {
thirdConsumer.obtain(value.get(thirdClazz));
}
}
Summarizing, we can say that in Java you can implement at the library level support for union types. But as a drawback, for each of the number of types need their own union & tort class and additionally save all types.
The full source code of the class can be found on github: