[JAVA] Point 37: Utilisez EnumMap au lieu de l'indexation ordinale

37. Utilisez EnumMap au lieu d'ordinal

Comme exemple d'utilisation de la méthode ordinale (Item35), considérons la classe Plant suivante.

class Plant {
    enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
 final String name;
    final LifeCycle lifeCycle;
     Plant(String name, LifeCycle lifeCycle) {
        this.name = name;
        this.lifeCycle = lifeCycle;
    }
 @Override public String toString() {
        return name;
    }
}

Supposons maintenant que vous souhaitiez créer un tableau de plantes pour chaque cycle de vie. Pour cela, vous pouvez effectuer les opérations suivantes en utilisant l'original.

// Using ordinal() to index into an array - DON'T DO THIS!
Set<Plant>[] plantsByLifeCycle =
    (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycle.length; i++)
    plantsByLifeCycle[i] = new HashSet<>();
 for (Plant p : garden)
    plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
 // Print the results
for (int i = 0; i < plantsByLifeCycle.length; i++) {
    System.out.printf("%s: %s%n",
        Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}

Le plus gros problème ici est que lorsque vous essayez d'accéder à un tableau, vous devez faire la sélection de valeur entière correcte pour le tableau localisé par l'original d'énumération. Si vous choisissez le mauvais numéro, vous obtiendrez une ArrayIndexOutOfBoundsException si vous avez de la chance, mais si vous ne l'êtes pas, le processus procédera avec le mauvais choix. Il existe une meilleure façon de réaliser la même chose. Étant donné que le tableau agit ici comme un Map qui obtient une valeur en utilisant enum comme clé, il est préférable d'utiliser Map pour l'implémentation. De plus, une implémentation efficace en tant que Map avec enum comme clé est effectuée dans EnumMap, nous verrons donc un exemple utilisant ceci ci-dessous.

// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>>  plantsByLifeCycle =
    new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values())
    plantsByLifeCycle.put(lc, new HashSet<>());
for (Plant p : garden)
    plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);

Cette implémentation est plus courte, plus facile à voir et plus sûre. Plus précisément, il présente les avantages suivants.

En outre, le constructeur EnumMap prend un objet Class comme argument, qui est un type borné et fournit des informations sur le type générique au moment de l'exécution (Item33). L'exemple précédent est encore plus court avec un flux (Item45):

// Naive stream-based approach - unlikely to produce an EnumMap!
System.out.println(Arrays.stream(garden)
        .collect(groupingBy(p -> p.lifeCycle)));

Le problème avec cette implémentation est qu'EnumMap n'est pas utilisé et ses performances sont inférieures à ce qu'elles seraient s'il était utilisé. Pour résoudre ce problème, affichez la carte qui est explicitement utilisée comme suit.

// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden)
        .collect(groupingBy(p -> p.lifeCycle,
            () -> new EnumMap<>(LifeCycle.class), toSet())));

Cette optimisation devient importante lorsque vous utilisez beaucoup Map.

Deux énumérations peuvent être représentées à l'aide d'un tableau de séquences qui utilisent les valeurs de ordinal. Le code source suivant est un exemple qui traite des changements entre deux états.

public enum Phase {
    SOLID, LIQUID, GAS;
    public enum Transition {
        MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
        // Rows indexed by from-ordinal, cols by to-ordinal
        private static final Transition[][] TRANSITIONS = { { null, MELT, SUBLIME }, { FREEZE, null, BOIL },
                { DEPOSIT, CONDENSE, null } };

        // Returns the phase transition from one phase to another
        public static Transition from(Phase from, Phase to) {
            return TRANSITIONS[from.ordinal()][to.ordinal()];
        }

    }
}

Il y a un problème avec ce programme. Le compilateur n'a aucun moyen de connaître la relation entre la valeur d'origine et l'index du tableau, et s'il ne parvient pas à créer le tableau ou oublie de mettre à jour le tableau avec la mise à jour des informations, il échoue à l'exécution ou ArrayIndexOutOfBoundsException Ou, `` NullPointerException '' se produit, ou le processus poursuit un comportement incorrect. L'énumération ci-dessus peut être mieux écrite en utilisant EnumMap.

// Using a nested EnumMap to associate data with enum pairs
public enum Phase {
    SOLID, LIQUID, GAS;
    public enum Transition {
        MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID), SUBLIME(SOLID,
                GAS), DEPOSIT(GAS, SOLID);
        private final Phase from;
        private final Phase to;

        Transition(Phase from, Phase to) {
            this.from = from;
            this.to = to;
        }

        // Initialize the phase transition map
        private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values())
                .collect(groupingBy(t -> t.from, () -> new EnumMap<>(Phase.class),
                        toMap(t -> t.to, t -> t, (x, y) -> y, () -> new EnumMap<>(Phase.class))));

        public static Transition from(Phase from, Phase to) {
            return m.get(from).get(to);
        }
    }
}

La partie initialisation de ce code est un peu compliquée. Le troisième argument de toMap, (x, y) -> y```, n'est pas utilisé et n'est nécessaire que pour obtenir EnumMap. Supposons maintenant que nous voulions définir un nouvel état, le plasma. Les définitions de transition d'état ajoutées ici sont l'ionisation et la désionisation. Dans une énumération basée sur un tableau, si vous essayez d'incorporer cette modification, ajoutez une nouvelle constante à Phase '', ajoutez deux constantes à Phase.Transition '' et 9 éléments Il est nécessaire de réécrire le tableau bidimensionnel qui était là pour qu'il comporte 16 éléments. D'autre part, pour une énumération basée sur ```EnumMap```, ajoutez une nouvelle constante à Phase et ajoutez deux constantes à `` `` Phase.Transition comme indiqué ci-dessous. Tout ce que vous avez à faire est de l'ajouter.

// Adding a new phase using the nested EnumMap implementation
public enum Phase {
    SOLID, LIQUID, GAS, PLASMA;
     public enum Transition {
        MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
        BOIL(LIQUID, GAS),   CONDENSE(GAS, LIQUID),
        SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID),
        IONIZE(GAS, PLASMA), DEIONIZE(PLASMA, GAS);
        ... // Remainder unchanged
    }
}

Ce code ne peut pas provoquer d'erreur humaine, et comme EnumMap utilise un tableau de tableaux, il n'y a pas de performances inférieures.

Recommended Posts

Point 37: Utilisez EnumMap au lieu de l'indexation ordinale
Élément 36: utiliser EnumSet au lieu de champs de bits
utiliser enum au lieu de la constante int
Point 71: éviter l'utilisation inutile d'exceptions vérifiées
Point 72: Favoriser l'utilisation d'exceptions standard
[Java] Pourquoi utiliser StringUtils.isEmpty () au lieu de String.isEmpty ()
Point 87: envisagez d'utiliser un formulaire sérialisé personnalisé
[Lire Java efficace] Chapitre 2 Item 1 "Considérez les méthodes de fabrique statiques plutôt que les constructeurs"
Élément 36: utiliser EnumSet au lieu de champs de bits
Point 37: Utilisez EnumMap au lieu de l'indexation ordinale
Point 44: Favoriser l'utilisation d'interfaces fonctionnelles standards
[Swift] Pourquoi FlowLayout devrait utiliser Delegate au lieu de l'instance
Utilisation de la classe Date
Point 53: Utilisez judicieusement les varargs
Point 45: Utilisez judicieusement les flux