Comments 57
Я знаю зачем в "C" в использовались union. В основном от дикой нехватки памяти и/или работе с raw бинарными данными вида "код типа данных" + union структур разных типов.
Дает и экономию памяти и упрощение "парсинга" данных, когда бинарный блок нужно "разложить" на "поля" по быстрому.
К слову, "неопределенное поведение" с union в "C" не вполне описывает ширину проблемы. Тут могут быть и аппаратные прерывания по ошибке (сегментирование памяти, не верный формат float и прочее процессорнозависимое)
union переползло и в C++.
Но к чему тянуть этот стиль в Java — я не понял.
Фраза "нужно чтобы объект в определенный момент содержал значения одного типа или значения другого типа." не описывает зачем это нужно.
Да можно создать псевдо аналог. Но зачем?
Только потому, что есть привычка работы с union? Или можно сделать, потому что можно сделать..
Это не троллинг. Я действительно не понимаю в каком case может понадобится такой Java union class (что нельзя решить другими более характерными для Java методами).
Не понял…
Union у Вас это инкапсулятор содержащий фактически ссылки на объект (один из).
Какая связь с экономией памяти?
Вы специально отвечаете не на тот комментарий?
Если в поле записано null — совершенно не важно насколько большой объект там мог бы быть записан, памяти требуется всего 4-8 байт. Один null никак не может быть больше двух Class<>
Использование двух Class<> нужно чтобы сохранять типы для корректных полей. Без них не обойтись.
Да как не обойтись-то? Я же ниже уже писал:
private T1 firstValue;
private T2 secondValue;
Ну и где тут вообще нужно сравнивать типы?
Кому нужно их проверять? Почему этого не может сделать компилятор?
В случае union нужно как-то проверять типы.
Это если не накладывать дополнительного ограничения, что только одно из полей может быть непустым.
public void setFirst(T1 value) {
first = value;
second = null;
}
public void setSecond(T2 value) {
first = null;
second = value;
}
public void setFirst(@NonNull T1 value) {
if (value == null) {
throw new NullPointerException("value is marked @NonNull but is null");
}
first = value;
second = null;
}
Вообще union в Си это очень низкоуровневая версия Типа-Суммы, в которой сам программист должен решать какой тип там лежит. Обычно это решается через идентификатор. Такой паттерн называют tagged union.
А сама эта фича пришла из функциональных языков программирования и в совокупности с паттерн матчингом позволяет достигать очень существенных улучшений кодовой базы.
То есть тут вообще не стоит вопроса об экономии памяти, эта абстракция прежде всего для программиста, а не для оптимизации.
Хотя попытки эмулировать эту фичу через шаблоны смотрятся убого(
Уточнение: union в Си это очень низкоуровневая версия Типа-Объединения. И то до него толком не дотягивает.
Вот tagged union — да, уже тип-сумма.
И нет, union как языковая конструкция в Си нужна именно для экономии памяти, поскольку никаких фич она не даёт, и tagged union можно и без неё реализовать.
Можно в принципе и на голых массивах сделать, но это не очень удобно и к тому же, тогда нужно будет вручную считать максимальную длину объединения.
Объединение является типом-суммой без явного тега (подразумеваемый есть). Канонический тип-сумма - это std::variant. Перечисление - тоже тип-сумма. "Тип-Объединение" - термин, выдуманный джаваскриптистами, в конструктивной теории типов есть только типы-суммы, типы-произведения и индуктивные типы ( с хвостом каких-то "заоблачных" вселенских типов).
А как бы вы реализовали, не используя дженерики?
Сейчас если метод может принимать MyDataInstance или MyDataRefid, где первое это весь объект целиком, а второе скажем id. В такой ситуации чаще всего метод объявляется принимающим Object, и внутри идет проверка типа инстанса. Это дублированием кода, тестированием, проблемы с добавлением новой поддерживаемой структуры, отсутсвие самодокументируемости кода и пр. Не знаю как у остальных, но я часто сталкиваюсь с таким кодом.
Был бы union можно было объявить метод myMethod(Union<MyDataInstance, MyDataRefid> data) и решить все проблемы компилятором. Я не согласен с реализацией идеи в статье, но в целом что-то такое наверное попробую.
myMethod(MyDataInstance data) {...}
myMethod(MyDataRefid refid) {
MyDataInstance data = getData(refid);
myMethod(data);
}
Не уточнил что чаще случается что такой класс обьявлен как generic, т.е. это инстанс ClassFoo<Long>
или ClassFoo<ClassBar>
. К сожалению в Java не получится сделать method overloading и метод объявляется как ClassFoo<Object>
, как на прием так и на результат, и дальше программист кастит куда нужно
Да, не забывайте что методы не только принимают, но и возвращают значения. Поэтому по факту получается:
myAction(MyDataInstance data) {...}
myAction(MyDataRefid refid) {...}
MyDataInstance readAsData() {...}
MyDataRefid readAsRef() {...}
Часто read не знает что он прочитает заранее, частая проблема в API и интеграции. Поэтому имеем
Object read() {...}
а потом опять куча ифов и кастингов после чтения.
Или в более реальном случае
<T> myAction(MyData<T> data, Class<T> type) {
if (type.isAssignable(Long.class)) then {...}
else if (type.isAssignable(Instance.class)) then {...}
else throw new IllegalArgumetnException()
}
myActionAsData(MyData<Instance> data) {...}
myActionAsRef(MyData<Ref> data) {...}
<T> MyData<T> read(Class<T> type) {
if (type.isAssignable(Long.class)) then {...}
else if (type.isAssignable(Instance.class)) then {...}
else throw new IllegalArgumentException()
}
MyData<Object> read() {...}
Так как это в коде это может быть использовано много где, к тому же объекты могут быть зависимы а значит перемножаются. Конечно можно так писать, так и делают, но в итоге код становится нечитаемым и дорогим в поддержке (особенно добавлении нового подтипа). Может быть с Union это будет легче поддерживаемо
Уточню еще что можно делать как угодно, все будет работать, проблема в том что это лишний код который приходится везде таскать и усложнять читаемость кода. В большинстве методов не важно же какой MyData<?>
приходит, и вообще такой подтип незачем светить наружу. В идеале всегда нетипизированый MyData который уже
class MyOtherDataAny extends Union<Long, MyOtherData> {};
class MyData {
MyOtherDataAny other;
}
Я может не совсем понял Вашу мысль. У меня был вопрос не про то как сделать, а про то, в каких случаях нужны такое "В такой ситуации чаще всего метод объявляется принимающим Object, и внутри идет проверка типа инстанса."
Т.е. совсем разнородные объекты, не объединенные даже общим интерфейсом.
Еще раз поясню. Не как реализовать. А в каких случаях (желательно пример) эта реализация может понадобится.
Я думал я именно это ответил. Попробую пояснить — в моем примере используются разнородные объекты, но по факту это разные отражения одной сущности. В одном случае это указатель на данные, другой это полные данные. Long и MyDataInstance.
Это конкретный пример из жизни, не выдуманный.
Простите за некоторое занудство.
Попробую переформулировать.
Для каких случаев может понадобится разнородные объекты в для хранения в одном union.
Коллекции списков объектов с одинаковыми свойствами (интерфейсами) — это понятно зачем. А вот коллекция объектов у которых только Object класс общий…
Как то выглядит концептуально не очень красиво (с моей точки зрения… не навязываю).
Поэтому мой вопрос был не про конкретную реализацию, а про саму необходимость программных реализаций union в Java.
Любую задачу можно решить разными путями.
Пример приведенный koowaah про void printType(String|Integer|Float val) не особо убеждает.
Ну чуть синтаксического сахара и проверки типов на этапе компиляции. Стоит ли оно того что бы городить программные реализации Union на Java.
Войдет в стандарт — ну хорошо. Не вошло еще. Так можно и без union делать.
Все можно сделать без union, оно так и сделано сейчас. Я нигде не утверждал что без него никак. Вообще все современные языки лишь дополнительное удобство, а технически все можно написать наверное и на Фортране.
Я не совсем понимаю чем мой ответ вас не устраивает, он же не выдуманный. Обычная ситуация когда внешний API в одном случае возвращает список с идентификаторами, в другом список с дынными. В одном случае лишь long во втором полноценный объект. Надо уметь работать и с тем и с тем.
PS с телефона треугольные скобки неправильно отображаются.
Это действительно не union. Это скорее проблема наследия Java 1.4 и способа появления дженериков при ее эволюции.
Да. Это раздражает сильно (недотипы с дженерик в языке).
Поскольку когда то активно писал на С/С++ (да и сейчас пишу под хобби и иногда сопровождаю/модифицирую легаси код на C++), то union воспринимаю, скорее в терминах C/C++.
Неужели вот такая конструкция "дешевле" просто второго поля в классе?
private final Class<T1> firstClass;
private final Class<T2> secondClass;
private Object value;
Сравните:
private T1 firstValue;
private T2 secondValue;
Концепция union типов стает популярной в языках.
Станет-то станет, но в чём был смысл делать тремя полями и сложно там, где можно было сделать двумя полями и просто?
Если не ошибаюсь, в jvm языке Ceylon давно есть union типы...
В скором времени завезут: Pattern-matching for switch, Sealed Types for Java Language.
Union biUnion = Union.of(Integer.class, CharSequence.class);
biUnion.set("Line");
(удалил, не та ветка)
Реализация union типов в Java