9
votes

Devrais-je essayer de créer une énumération réversible en Java ou y a-t-il une meilleure façon?

Cela semble avoir été confronté à ce problème à plusieurs reprises et je voulais demander à la communauté si je n'aborde que le mauvais arbre. Fondamentalement, ma question peut être distillée à ceci: si j'ai une énumération (en Java) pour laquelle les valeurs sont importantes, devrais-je utiliser un énumé ou y avoir une meilleure façon, et si je fais une meilleure façon d'utiliser un énum. est le meilleur moyen d'inverser la recherche?

Voici un exemple. Supposons que je souhaite créer un haricot représentant un mois et une année spécifiques. Je pourrais créer quelque chose comme ce qui suit: p> xxx pré>

Ici, je stocke mon mois comme une classe distincte appelée mois, de sorte qu'il soit de sécurité. Si je viens de mettre INT, alors tout le monde pourrait passer à 13 ou 5 643 ou à -100 en tant que nombre, et il n'y aurait aucun moyen de vérifier cela au moment de la compilation. Je les limite à mettre un mois que je vais implémenter comme un énorme: p> xxx pré>

suppose maintenant que j'ai une base de données de backend que je veux écrire à, qui n'accepte que la forme entier. Eh bien, la façon standard de faire cela semble être: p> xxx pré>

assez simple, mais que se passe-t-il si je veux lire ces valeurs de la base de données et les écrire? Je pourrais simplement mettre en œuvre une fonction statique à l'aide d'une déclaration de cas au sein de l'ENUM qui prend un Int et retourne l'objet du mois respectif. Mais cela signifie que si j'ai changé quelque chose, je devrais alors changer cette fonction ainsi que les arguments du constructeur - Changement de deux endroits. Alors voici ce que je faisais. Tout d'abord, j'ai créé une classe de carte réversible comme suit: p> xxx pré>

puis j'ai implémenté cela dans mon énumé au lieu de la méthode du constructeur: P>

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;

    private static ReversibleHashMap<java.lang.Integer,Month> monthNumMap;

    static {
        monthNumMap = new ReversibleHashMap<java.lang.Integer,Month>();
        monthNumMap.put(new java.lang.Integer(1),JANUARY);
        monthNumMap.put(new java.lang.Integer(2),FEBRUARY);
        monthNumMap.put(new java.lang.Integer(3),MARCH);
        monthNumMap.put(new java.lang.Integer(4),APRIL);
        monthNumMap.put(new java.lang.Integer(5),MAY);
        monthNumMap.put(new java.lang.Integer(6),JUNE);
        monthNumMap.put(new java.lang.Integer(7),JULY);
        monthNumMap.put(new java.lang.Integer(8),AUGUST);
        monthNumMap.put(new java.lang.Integer(9),SEPTEMBER);
        monthNumMap.put(new java.lang.Integer(10),OCTOBER);
        monthNumMap.put(new java.lang.Integer(11),NOVEMBER);
        monthNumMap.put(new java.lang.Integer(12),DECEMBER);
    }

    public int getMonthNum() {
        return monthNumMap.reverseGet(this);
    }

    public static Month fromInt(int monthNum) {
        return monthNumMap.get(new java.lang.Integer(monthNum));
    }
}


1 commentaires

Cette question est très similaire à Stackoverflow .com / Questions / 5021246 / ... , qui traite également de la manière d'associer chaque valeur Enum avec une valeur INT et comment convertir les deux manières.


4 Réponses :


4
votes

Il y a un moyen plus facile de le faire. Chaque Enum a un Ordinal () code> La méthode renvoie son numéro (à partir de zéro).

Month.values()[ordinalNumber];


2 commentaires

Un inconvénient d'appeler mois.values ​​() [OrdinalNumber] est qu'il doit créer une copie de la matrice à chaque fois. Ce serait bien si Java exposerait une propriété de liste immuable comme des valeurs () qui pourraient simplement renvoyer une référence à la même liste sur chaque appel.


Peut-être aussi mais de développer une solution pour ce "problème" - sauf si vous faites cela des millions de fois - est une micro-optimisation inutile.



11
votes

Ceci est un modèle très courant, et c'est bien pour Enums ... mais cela peut être mis en œuvre plus simplement. Il n'y a pas besoin d'une "carte réversible" - la version qui prend le numéro de mois dans le constructeur est préférable d'aller de mois à int . Mais aller dans l'autre sens n'est pas trop difficile non plus: xxx

dans le cas spécifique de chiffres que vous connaissez ira de 1 à n, vous pouvez simplement utiliser un tableau - soit en prenant Mois.Values ​​() [valeur - 1] ou mettant en cache la valeur de retour de mois.values ​​() pour empêcher la création d'un nouveau tableau sur chaque appel. (Et comme le dit Cletus, getmontandnum pourrait simplement retourner ordinal () + 1 .)

Cependant, il vaut la peine d'être conscient du modèle ci-dessus dans la Un cas plus général où les valeurs peuvent être irrecevables, ou peu répandues.

Il est important de noter que l'initialiseur statique est exécuté après toutes les valeurs ENUM sont créées. Il serait agréable de simplement écrire xxx

dans le constructeur et ajouter une variable de variable statique pour numberomonthmap , mais cela ne fonctionne pas - vous " d Obtenez un NullReferenceException immédiatement, car vous essayiez de mettre la valeur dans une carte qui n'existait pas encore: (


4 commentaires

Dans ce cas particulier, cela n'ajoute pas beaucoup de valeur - mais j'ai utilisé ce modèle sur et encore. (C'est une douleur qu'il est difficile d'extraire une classe d'assistance.)


Merci à tous pour vos commentaires. Jon - J'ai mieux aimé votre solution et je n'avais pas pensé à utiliser une boucle de généraliste pour peupler la carte. L'exemple que j'ai donné est assez spécifique en ce que les représentations internes sont des entiers et ils sont dans l'ordre exact donné. Cependant, si j'utilisais des caractères ou des chiffres avec des espaces entre eux, etc., je ne suis pas en mesure d'utiliser la solution ordinale (), je dois donc utiliser quelque chose de similaire à ce que fournit Jon. Et je suis d'accord avec lui que c'est difficile, il n'y a aucun moyen de créer une classe de base avec cette fonctionnalité intégrée. Mais je vais créer un modèle dans mon IDE :)


Voir aussi: Deepjava.wordpress.com/2006/ 12/08 / ...


J'ai eu un peu quelques fois avec ça. Un seul était celui où j'avais un protocole de communication et j'ai eu des codes de message cartographiés à l'ordinal. Il n'a pas cassé, mais quand j'avais des codes comme adduser à la position 6 et remonatère à côté d'elle à 7, puis je devais ajouter une nouvelle personne comme Moduser en position 20. Ils auraient vraiment dû être regroupés logiquement, mais je ne pouvais pas le faire car il aurait déplacé de nombreux autres numéros de code. Je sais que des mois ne changent pas souvent, mais beaucoup de choses réelles du monde font (quels que soient les utilisateurs de l'entreprise, ils ne veulent pas !!)



1
votes

Vous ne devez pas utiliser ordinal () pour ce genre de chose, pour l'échantillon avec des mois, cela fonctionnerait (car il ne sera pas prolongé), mais l'une des bonnes choses avec Enums en Java est qu'ils sont conçus pour être conçus pour être conçus possible d'étendre sans casser des choses. Si vous commencez à compter sur l'ordinal (), les choses vont casser si vous ajoutez une certaine valeur au milieu.

Je le ferais comme Jon Skeet suggère (il l'a écrit pendant que j'écrivais cela) mais pour les cas où la représentation du nombre interne se trouve dans une plage bien définie de 0 à 20 (ou quelque chose), je ne voudrais probablement pas utiliser de Hashmap et introduisez l'autoboxage de l'INT, mais utilisez plutôt un tableau ordinaire (comme mois [12]) mais les deux sont corrects (Jon a changé plus tard son poste pour inclure cette suggestion).

EDIT: Pour les rares Enums où il y a un ordre naturel (comme des mois triés) ordinal () est probablement en sécurité à utiliser. Les problèmes que vous risquez de courir si vous le persistez, il apparaîtra pour les choses où quelqu'un pourrait changer l'ordre de l'énum. Comme si: "Enum {masculin, femme}" devient un "Enum {inconnu, une femme, un homme}" lorsque quelqu'un prolonge le programme à l'avenir ne sachant pas que vous comptez sur l'ordinal.

Donner à Jon A +1 pour écrire la même chose que j'écris seulement.


2 commentaires

@cletus: C'est subjectif et je suis en désaccord. Était-ce la raison du bowvote? Il y a une explication assez complète de la raison pour laquelle vous ne devez jamais compter sur l'ordinal si vous prenez une copie d'une copie de Java efficace (ou de lire l'élément 31 ici: books.google.se/... )


@Fredrik: Je pense que quelqu'un a évolué toutes les trois réponses, même si je ne sais pas pourquoi.



3
votes

Je suis probablement loin derrière le sac dans une réponse ici, mais j'ai tendance à en mettre un peu plus simple. N'oubliez pas que «Enum» a une méthode valeurs ().

public static Month parse(int num)
{
  for(Month value : values())
  {
    if (value.monthNum == num)
    {
      return value;
    }
  }
  return null; //or throw exception if you're of that mindset
}


0 commentaires