Implementación de tipos de unión en Java

Al desarrollar código, a veces es necesario que un objeto contenga valores de un tipo o valores de otro tipo en algún momento. Los lenguajes de programación que admiten el concepto de uniones permiten en un momento determinado guardar el valor actual en un área de memoria.

Por ejemplo, en C / C ++ puedes escribir así.

union value { int i; float f; }; union value v; vi = 5; /* vf - undefined behaivor */ 

Además, si establecemos el valor en un campo, leer el valor de otro campo tendrá un comportamiento indefinido.

Para simplificar el trabajo con tipos de unión en C ++ 17, se agregó la clase std :: variant.

  std::variant<int, float> v { 5 }; std::cout << "int value: " << std::get<int>(v) << std::endl; 

El lenguaje Java no admite tipos de unión. Como alternativa, puede implementar una clase de datos con dos campos de ciertos tipos con setters y getters. Pero quería que el valor se almacenara en un campo, y no en dos.

Como sabe, el tipo de objeto puede guardar el valor de un tipo y luego reasignar los valores de otro tipo. Y esto se puede usar para implementar una clase, como la clase std :: variant.

Dado que en Java no puede especificar un número variable de tipos en un genérico, para un cierto número de tipos necesita especializar una clase (Union2, Union3, etc.). Escribamos la clase principal Unión y sus operaciones básicas.

  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(); } 

Para crear objetos de clase, utilizaremos métodos de fábrica. Dependiendo del número de tipos, se devolverá la especialización específica de la clase.

  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); } 

La especialización concreta de la clase de unión almacenará un cierto número de tipos y un objeto de campo. Si indicamos un tipo incorrecto, obtendremos un error.

  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(); } } 

Ahora veamos un ejemplo de cómo usar esta clase. Como puede ver, Union no funciona con especializaciones específicas, lo que simplifica el código.

  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)); 

También puede escribir un visitante simple para verificar el valor actual.

  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)); } } 

En resumen, podemos decir que en el lenguaje Java, el soporte para los tipos de unión se puede implementar a nivel de biblioteca. Pero como inconveniente, para cada número de tipos necesita su propia especialización de la clase de unión y, además, guardar todos los tipos.

El código fuente completo de la clase se puede ver en github: code

Source: https://habr.com/ru/post/465097/


All Articles