Lors du développement de code, il est parfois nécessaire qu'un objet contienne des valeurs d'un type ou des valeurs d'un autre type à un moment donné. Les langages de programmation qui prennent en charge le concept d'unions permettent à un certain moment de sauvegarder la valeur actuelle dans une zone mémoire.
Par exemple, en C / C ++, vous pouvez écrire comme ceci.
union value { int i; float f; }; union value v; vi = 5;
De plus, si nous définissons la valeur sur un champ, la lecture de la valeur d'un autre champ aura un comportement indéfini.
Pour simplifier le travail avec les types d'union en C ++ 17, la classe std :: variant a été ajoutée.
std::variant<int, float> v { 5 }; std::cout << "int value: " << std::get<int>(v) << std::endl;
Le langage Java ne prend pas en charge les types d'union. Comme alternative, vous pouvez implémenter une classe de données avec deux champs de certains types avec des setters et des getters. Mais je voulais que la valeur soit stockée dans un champ, et non dans deux.
Comme vous le savez, le type d'objet peut enregistrer la valeur d'un type, puis réaffecter les valeurs d'un autre type. Et cela peut être utilisé pour implémenter une classe, comme la classe std :: variant.
Étant donné qu'en Java, vous ne pouvez pas spécifier un nombre variable de types dans un générique, pour un certain nombre de types, vous avez besoin d'une spécialisation de la classe (Union2, Union3, etc.). Écrivons la classe principale Union et ses opérations de base.
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(); }
Pour créer des objets de classe, nous utiliserons des méthodes d'usine. Selon le nombre de types, la spécialisation spécifique de la classe sera retournée.
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 spécialisation concrète de la classe union stockera un certain nombre de types et un champ Object. Si nous indiquons un type incorrect, nous obtiendrons une erreur.
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(); } }
Voyons maintenant un exemple d'utilisation de cette classe. Comme vous pouvez le voir, Union ne fonctionne pas avec des spécialisations spécifiques, ce qui rend le code plus simple.
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));
Vous pouvez également écrire un simple visiteur pour vérifier la valeur actuelle.
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 résumé, nous pouvons dire que dans le langage Java, la prise en charge des types d'union peut être implémentée au niveau de la bibliothèque. Mais comme inconvénient, pour chaque nombre de types, vous avez besoin de votre propre spécialisation de la classe union et enregistrez également tous les types.
Le code source complet de la classe peut être consulté sur github:
code