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

от автора

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

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

  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


ссылка на оригинал статьи https://habr.com/ru/post/465097/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *