Ao desenvolver código, às vezes é necessário que um objeto contenha valores de um tipo ou valores de outro tipo em algum momento. As linguagens de programação que suportam o conceito de união permitem, em um determinado momento, salvar o valor atual em uma área de memória.
Por exemplo, em C / C ++, você pode escrever assim.
union value { int i; float f; }; union value v; vi = 5;
Além disso, se definirmos o valor para um campo, a leitura do valor de outro campo terá um comportamento indefinido.
Para simplificar o trabalho com tipos de união em C ++ 17, a classe std :: variant foi adicionada.
std::variant<int, float> v { 5 }; std::cout << "int value: " << std::get<int>(v) << std::endl;
A linguagem Java não suporta tipos de união. Como alternativa, você pode implementar uma classe de dados com dois campos de certos tipos com setters e getters. Mas eu queria que o valor fosse armazenado em um campo, e não em dois.
Como você sabe, o tipo de objeto pode salvar o valor de um tipo e reatribuir os valores de outro tipo. E isso pode ser usado para implementar uma classe, como a classe std :: variant.
Como em Java, você não pode especificar um número variável de tipos em um genérico, para um certo número de tipos, você precisa de uma especialização da classe (Union2, Union3, etc.). Vamos escrever a classe principal Union e suas operações 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 criar objetos de classe, usaremos métodos de fábrica. Dependendo do número de tipos, a especialização específica da classe será retornada.
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); }
A especialização concreta da classe union armazenará um certo número de tipos e um campo Object. Se indicarmos um tipo incorreto, obteremos um erro.
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(); } }
Agora vamos ver um exemplo de como usar essa classe. Como você pode ver, o Union não funciona com especializações específicas, o que simplifica o 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));
Você também pode escrever um visitante simples para verificar o valor atual.
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)); } }
Resumindo, podemos dizer que na linguagem Java, o suporte para tipos de união pode ser implementado no nível da biblioteca. Mas como uma desvantagem, para cada número de tipos, você precisa de sua própria especialização de classe union e, além disso, salva todos os tipos.
O código fonte completo da classe pode ser visualizado no github:
code