Как стать автором
Обновить

Реализация union типов в Java

Время на прочтение5 мин
Количество просмотров8.2K
При разработке кода иногда нужно чтобы объект в определенный момент содержал значения одного типа или значения другого типа. Языки программирования, которые поддерживают концепцию объединений, позволяют в определенный момент сохранять текущее значение в одной области памяти.

Например, в языках С/С++ можно написать вот так.

  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
Теги:
Хабы:
Всего голосов 15: ↑7 и ↓8-1
Комментарии57

Публикации

Истории

Работа

Java разработчик
347 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань