Как стать автором
Поиск
Написать публикацию
Обновить

Комментарии 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 нужно как-то проверять типы.

А если внутри union два поля?

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

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

А как вы это понимаете?
НЛО прилетело и опубликовало эту надпись здесь

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

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

Ну так это особенность именно что 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)
Как вариант можно. Но что если класс реализует много интерфейсов?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации