При разработке кода иногда нужно чтобы объект в определенный момент содержал значения одного типа или значения другого типа. Языки программирования, которые поддерживают концепцию объединений, позволяют в определенный момент сохранять текущее значение в одной области памяти.
Например, в языках С/С++ можно написать вот так.
При этом если мы установили значение одному полю, то считывание значения другого поля будет будет иметь неопределеное поведения.
Для упрощения работы с union типамы в С++17 был добавлен класс std::variant.
Язык Java не поддерживает union типы. Как альтернативу, можно реализовать дата-класс с двумя полями определенных типов с сеттерами и геттерами. Но хотелось чтобы значение сохранялось в одном поле, а не в двух.
Как известно типу Object можно сохранить значение одного типа, а потом переприсвоить значения другого типа. И это можно использовать для реализации класса, наподобие к классу std::variant.
Поскольку в языке Java нельзя указать переменное число типов в дженерике, то для определенного количества типов нужна специализация класса(Union2, Union3 и тд.). Напишим основной класс Union и базовые его операции.
Для создания объектов класса будем использовать фабричные методы. В зависимости от количества типов будет возращаться конкретная специализация класса.
Конкретная специализация union класса будет сохранять определенное количество типов и одно поле Object. В случае если мы указывает не корректный тип, то получим ошибку.
А теперь посмотрим на примере как можно использовать этот класс. Как можно заметить не работает с конкретными специализации Union, что делает код проще.
Также для проверки текущего значения можно написать простой визитер.
Подводя итоги, можно сказать что в языке Java можно реализовать на уровне библиотеки поддержку union типов. Но как недостаток, для каждого количества типов нужна своя специализиация union класса и дополнительно сохранять все типы.
Полный исходной код класса можно посмотреть на github: code
Например, в языках С/С++ можно написать вот так.
union value {
int i;
float f;
};
union value v;
v.i = 5; /* v.f - undefined behaivor */
При этом если мы установили значение одному полю, то считывание значения другого поля будет будет иметь неопределеное поведения.
Для упрощения работы с union типамы в С++17 был добавлен класс std::variant.
std::variant<int, float> v { 5 };
std::cout << "int value: " << std::get<int>(v) << std::endl;
Язык Java не поддерживает union типы. Как альтернативу, можно реализовать дата-класс с двумя полями определенных типов с сеттерами и геттерами. Но хотелось чтобы значение сохранялось в одном поле, а не в двух.
Как известно типу Object можно сохранить значение одного типа, а потом переприсвоить значения другого типа. И это можно использовать для реализации класса, наподобие к классу std::variant.
Поскольку в языке Java нельзя указать переменное число типов в дженерике, то для определенного количества типов нужна специализация класса(Union2, Union3 и тд.). Напишим основной класс Union и базовые его операции.
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();
}
Для создания объектов класса будем использовать фабричные методы. В зависимости от количества типов будет возращаться конкретная специализация класса.
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);
}
Конкретная специализация union класса будет сохранять определенное количество типов и одно поле Object. В случае если мы указывает не корректный тип, то получим ошибку.
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 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 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();
}
}
А теперь посмотрим на примере как можно использовать этот класс. Как можно заметить не работает с конкретными специализации Union, что делает код проще.
Union triUnion = 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));
Также для проверки текущего значения можно написать простой визитер.
Union biUnion = Union.of(Integer.class, String.class);
biUnion.set("Line");
Union triUnion = 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));
}
}
Подводя итоги, можно сказать что в языке Java можно реализовать на уровне библиотеки поддержку union типов. Но как недостаток, для каждого количества типов нужна своя специализиация union класса и дополнительно сохранять все типы.
Полный исходной код класса можно посмотреть на github: code