Pull to refresh

Comments 57

Я знаю зачем в "C" в использовались union. В основном от дикой нехватки памяти и/или работе с raw бинарными данными вида "код типа данных" + union структур разных типов.
Дает и экономию памяти и упрощение "парсинга" данных, когда бинарный блок нужно "разложить" на "поля" по быстрому.


К слову, "неопределенное поведение" с union в "C" не вполне описывает ширину проблемы. Тут могут быть и аппаратные прерывания по ошибке (сегментирование памяти, не верный формат float и прочее процессорнозависимое)
union переползло и в C++.


Но к чему тянуть этот стиль в Java — я не понял.
Фраза "нужно чтобы объект в определенный момент содержал значения одного типа или значения другого типа." не описывает зачем это нужно.


Да можно создать псевдо аналог. Но зачем?
Только потому, что есть привычка работы с union? Или можно сделать, потому что можно сделать..


Это не троллинг. Я действительно не понимаю в каком case может понадобится такой Java union class (что нельзя решить другими более характерными для Java методами).

Если два или более объектов большие, а Class занимают занимают менее памяти. В таком варианте можно использовать.

Не понял…
Union у Вас это инкапсулятор содержащий фактически ссылки на объект (один из).
Какая связь с экономией памяти?

В джаве как минимум нужно хранить список допустимых типов, чтобы проверять корректность при считывании или записи.

Вы специально отвечаете не на тот комментарий?


Если в поле записано null — совершенно не важно насколько большой объект там мог бы быть записан, памяти требуется всего 4-8 байт. Один null никак не может быть больше двух Class<>

В случае null это верно. Можно при передачи объекта null, кидать исключения.
Использование двух Class<> нужно чтобы сохранять типы для корректных полей. Без них не обойтись.

Да как не обойтись-то? Я же ниже уже писал:


        private T1 firstValue;
        private T2 secondValue;

Ну и где тут вообще нужно сравнивать типы?

Если имеется например, union с двух типов — Union2<String, Integer>. И хотим записать вещественное число. Нужно как то проверять типы. Для этого и нужны два поля Class<>.

Кому нужно их проверять? Почему этого не может сделать компилятор?

В случае двух полей разных типов сравнивать не нужно.
В случае union нужно как-то проверять типы.
В определенный момент может сохранять только одно значение определенного типа в поле Object value. Я показал только для Union2, Union3. Можно специализацию сделать и для больше типов.

Вы сейчас ответили не на вопрос "Почему компилятор не может проверить типы сам, если внутри Union2 два поля?", а на какой-то другой.

UFO landed and left these words here

Это если не накладывать дополнительного ограничения, что только одно из полей может быть непустым.

UFO landed and left these words here
Через сеттеры?
public void setFirst(T1 value) {
first = value;
second = null;
}
public void setSecond(T2 value) {
first = null;
second = value;
}
UFO landed and left these words here
Если такая ситуация является недопустимой, можно добавить проверку на null.
public void setFirst(@NonNull T1 value) {
  if (value == null) {
    throw new NullPointerException("value is marked @NonNull but is null");
  }
  first = value;
  second = null;
}
UFO landed and left these words here

Ну так это особенность именно что null. Не просто же так null называют ошибкой на миллион долларов.

Вообще union в Си это очень низкоуровневая версия Типа-Суммы, в которой сам программист должен решать какой тип там лежит. Обычно это решается через идентификатор. Такой паттерн называют tagged union.
А сама эта фича пришла из функциональных языков программирования и в совокупности с паттерн матчингом позволяет достигать очень существенных улучшений кодовой базы.
То есть тут вообще не стоит вопроса об экономии памяти, эта абстракция прежде всего для программиста, а не для оптимизации.
Хотя попытки эмулировать эту фичу через шаблоны смотрятся убого(

Уточнение: union в Си это очень низкоуровневая версия Типа-Объединения. И то до него толком не дотягивает.


Вот tagged union — да, уже тип-сумма.


И нет, union как языковая конструкция в Си нужна именно для экономии памяти, поскольку никаких фич она не даёт, и tagged union можно и без неё реализовать.

Можно в принципе и на голых массивах сделать, но это не очень удобно и к тому же, тогда нужно будет вручную считать максимальную длину объединения.

Можете привести пример, как это можно сделать.

Объединение является типом-суммой без явного тега (подразумеваемый есть). Канонический тип-сумма - это std::variant. Перечисление - тоже тип-сумма. "Тип-Объединение" - термин, выдуманный джаваскриптистами, в конструктивной теории типов есть только типы-суммы, типы-произведения и индуктивные типы ( с хвостом каких-то "заоблачных" вселенских типов).

Нет разницы кто придумал термин если термин существует и имеет вполне определённое значение.

По поводу union+pattern matching я с вами согласен.
А как бы вы реализовали, не используя дженерики?

Как фичу языка, введя новое ключевое слово или как поступили в Rust и Swift: переиспользовав существующее слово enum.

На мой взгляд это полезно для структурирования кода.

Сейчас если метод может принимать 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 начали добавлять во многие языки и может быть в Java в будущем тоже добавят. Поживем, увидим.

Все можно сделать без union, оно так и сделано сейчас. Я нигде не утверждал что без него никак. Вообще все современные языки лишь дополнительное удобство, а технически все можно написать наверное и на Фортране.


Я не совсем понимаю чем мой ответ вас не устраивает, он же не выдуманный. Обычная ситуация когда внешний API в одном случае возвращает список с идентификаторами, в другом список с дынными. В одном случае лишь long во втором полноценный объект. Надо уметь работать и с тем и с тем.

Я напишу своё понимание проблемы, из за удаления информации о дженериках в рантайме, нельзя написать два метода с одним именем один из которых принимает SomeContainer[Integer], а другой SomeContainer[String] и введение конструкции SomeContainer[Integer|String] помогло бы. Но это не совсем union в классическом понимании.
PS с телефона треугольные скобки неправильно отображаются.

Это действительно не union. Это скорее проблема наследия Java 1.4 и способа появления дженериков при ее эволюции.
Да. Это раздражает сильно (недотипы с дженерик в языке).


Поскольку когда то активно писал на С/С++ (да и сейчас пишу под хобби и иногда сопровождаю/модифицирую легаси код на C++), то union воспринимаю, скорее в терминах C/C++.

При восприятии идеи типа-объединения или типа-суммы в терминах C++ надо смотреть на тип данных std::variant, а не на ключевое слово union...

Неужели вот такая конструкция "дешевле" просто второго поля в классе?


        private final Class<T1> firstClass;
        private final Class<T2> secondClass;
        private Object value;

Сравните:


        private T1 firstValue;
        private T2 secondValue;
В языке Scala 3.0 хотят завести union типы. Если не ошибаюсь в TypeScript уже есть union типы.
Концепция union типов стает популярной в языках.

Станет-то станет, но в чём был смысл делать тремя полями и сложно там, где можно было сделать двумя полями и просто?

Да. В Ceylon поддерживаются union типы.

void printType(String|Integer|Float val) {
    switch (val)
    case (is String) { print("String: ``val``"); }
    case (is Integer) { print("Integer: ``val``"); }
    case (is Float) { print("Float: ``val``"); }
}
Эти классные фичи. Очень бы упростили написание кода.
Ага, ага… Ява все больше напоминает холеную корову, на которую напялили несколько дорогих стампидных седел…
Pattern matching в Java хотят завезти. В добавок хотят добавить record & sealed type.
Я думаю Java правильно развивается. Это упростит много кода.
Скажите что будет если я попробую запустить такой код?
Union biUnion = Union.of(Integer.class, CharSequence.class);
biUnion.set("Line");
Он сравнивает с типами String и Integer. Но не сравнивает с реализуемыми интерфейсами.
А почему вы не используете?
firstClass.isInstance(value)
Как вариант можно. Но что если класс реализует много интерфейсов?
Sign up to leave a comment.

Articles