Convertir une valeur entière en une énumération Java correspondante

86

J'ai une énumération comme celle-ci:

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);
    private final int value;   

    PcapLinkType(int value) {
        this.value= value;
    }
}

Maintenant, j'obtiens un int d'une entrée externe et je veux l'entrée correspondante - lancer une exception si une valeur n'existe pas est ok, mais de préférence je l'aurais DLT_UNKNOWN dans ce cas.

int val = in.readInt();
PcapLinkType type = ???; /*convert val to a PcapLinkType */
Lyke
la source

Réponses:

105

Vous devrez le faire manuellement, en ajoutant une carte statique dans la classe qui mappe des entiers à des énumérations, telles que

private static final Map<Integer, PcapLinkType> intToTypeMap = new HashMap<Integer, PcapLinkType>();
static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.value, type);
    }
}

public static PcapLinkType fromInt(int i) {
    PcapLinkType type = intToTypeMap.get(Integer.valueOf(i));
    if (type == null) 
        return PcapLinkType.DLT_UNKNOWN;
    return type;
}
MoiBigFatGuy
la source
1
mis à jour avec les recommandations de dty, ce qui était une bonne idée.
MeBigFatGuy
J'espère que vous avez d'abord exécuté mon code dans un compilateur ... Je viens de l'inventer par le haut de ma tête. Je sais que la technique fonctionne - je l'ai utilisée hier. Mais le code est sur une autre machine et celle-ci n'a pas mes outils de développement.
dty
1
allOf est uniquement disponible pour les ensembles
MeBigFatGuy
1
, Aussi EnumMaputilise les énumérations comme les clés. Dans ce cas, l'OP veut les énumérations comme valeurs.
dty
8
Cela semble être beaucoup de frais généraux inutiles. Ceux qui ont réellement besoin de ce type d'opération ont probablement besoin de hautes performances car ils écrivent / lisent à partir de flux / sockets, auquel cas, la mise en cache de values()(si vos valeurs d'énumération sont séquentielles) ou une simple switchinstruction battrait cette méthode facilement. . Si vous n'avez qu'une poignée d'entrées dans votre, Enumcela n'a pas beaucoup de sens d'ajouter la surcharge d'un HashMap simplement pour la commodité de ne pas avoir à mettre à jour l' switchinstruction. Cette méthode peut sembler plus élégante, mais c'est aussi un gaspillage.
écraser
30

Il existe une méthode statique values()qui est documentée, mais pas là où vous vous attendez: http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

enum MyEnum {
    FIRST, SECOND, THIRD;
    private static MyEnum[] allValues = values();
    public static MyEnum fromOrdinal(int n) {return allValues[n];}
}

En principe, vous pouvez utiliser juste values()[i], mais il y a des rumeurs qui values()créeront une copie du tableau à chaque fois qu'il est appelé.

18446744073709551615
la source
9
Selon Joshua Bloch (Effective Java Book) : Ne jamais dériver une valeur associée à une énumération de son ordinal; Votre implémentation ne doit pas reposer sur l'ordre des énumérations.
stevo.mettre le
4
Mise en œuvre de quoi? Si nous implémentons un algorithme, l'implémentation ne devrait pas s'appuyer sur l'ordre des énumérations à moins que cet ordre ne soit documenté. Lorsque nous implémentons l'énumération elle-même, il est correct d'utiliser ces détails d'implémentation, de la même manière que d'utiliser des méthodes privées de classe.
18446744073709551615
1
Ne suis pas d’accord. Je crois que jamais n'est signifié quelle que soit la documentation. Vous ne devez pas utiliser d'ordinaux même lorsque vous implémentez vous-même enum. C'est une mauvaise odeur et c'est sujet aux erreurs. Je ne suis pas un expert mais je ne discuterais pas avec Joshua Bloch :)
stevo.mit le
4
@ stevo.mit jetez un œil à la nouvelle énumération java.time.Month en Java 8. La méthode statique Month.of (int) fait exactement ce que Joshua Bloch a dit que vous ne devriez "jamais" faire. Il renvoie un mois en fonction de son ordinal.
Klitos Kyriacou
1
@ stevo.mit Il existe des énumérations ordonnées et des énumérations non ordonnées . (Et les énumérations de bitmask aussi.) Il est tout simplement incorrect de parler d'elles comme de simples «énumérations». Le choix des moyens expressifs à utiliser doit être basé sur le niveau d'abstraction sur lequel vous travaillez. Il est en effet incorrect d'utiliser des détails d'implémentation (moyens expressifs du niveau inférieur) ou des hypothèses d'utilisation (moyens expressifs du niveau supérieur). Quant à « jamais », dans les langues humaines, cela ne veut jamais dire jamais, car il y a toujours un contexte. (Habituellement, dans la programmation d'application, jamais ...) BTW, programering.com/a/MzNxQjMwATM.html
18446744073709551615
14

Vous devrez créer une nouvelle méthode statique où vous itérez PcapLinkType.values ​​() et comparez:

public static PcapLinkType forCode(int code) {
    for (PcapLinkType typе : PcapLinkType.values()) {
        if (type.getValue() == code) {
            return type;
        }
    }
    return null;
 }

Ce serait bien s'il est appelé rarement. S'il est appelé fréquemment, regardez l' Mapoptimisation suggérée par d'autres.

Bozho
la source
4
Cela pourrait coûter cher si on l'appelle beaucoup. Construire une carte statique est susceptible de donner un meilleur coût amorti.
dty
@dty o (n) avec n = 200 - je ne pense pas que ce soit un problème
Bozho
7
C'est une déclaration totalement ridicule sans une idée de la fréquence à laquelle elle est appelée. Si c'est appelé une fois, très bien. Si elle est appelée pour chaque paquet passant sur un réseau 10Ge, il est très important de rendre un algorithme 200 fois plus rapide. D'où la raison pour laquelle j'ai qualifié ma déclaration par "si on
m'appelle
10

Vous pouvez faire quelque chose comme ceci pour les enregistrer automatiquement dans une collection avec laquelle convertir ensuite facilement les entiers en énumération correspondante. (BTW, les ajouter à la carte dans le constructeur enum n'est pas autorisé . C'est bien d'apprendre de nouvelles choses même après de nombreuses années d'utilisation de Java. :)

public enum PcapLinkType {
    DLT_NULL(0),
    DLT_EN10MB(1),
    DLT_EN3MB(2),
    DLT_AX25(3),
    /*snip, 200 more enums, not always consecutive.*/
    DLT_UNKNOWN(-1);

    private static final Map<Integer, PcapLinkType> typesByValue = new HashMap<Integer, PcapLinkType>();

    static {
        for (PcapLinkType type : PcapLinkType.values()) {
            typesByValue.put(type.value, type);
        }
    }

    private final int value;

    private PcapLinkType(int value) {
        this.value = value;
    }

    public static PcapLinkType forValue(int value) {
        return typesByValue.get(value);
    }
}
Esko Luontola
la source
1
C'est ce que vous obtenez pour vérifier votre réponse avant de publier. ;)
Esko Luontola
10

si vous avez une énumération comme celle-ci

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  DLT_UNKNOWN(-1);

    private final int value;   

    PcapLinkType(int value) {
        this.value= value;
    }
}

alors vous pouvez l'utiliser comme

PcapLinkType type = PcapLinkType.values()[1]; /*convert val to a PcapLinkType */
Jack Gajanan
la source
vous avez manqué le commentaire / * snip, 200 autres énumérations, pas toujours consécutives. * /
MeBigFatGuy
juste au cas où votre valeur d'énumération serait la transitivité de Zero, c'est une mauvaise pratique
cuasodayleo
4

Comme le dit @MeBigFatGuy, sauf que vous pouvez faire en sorte que votre static {...}bloc utilise une boucle sur la values()collection:

static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.getValue(), type);
    }
}
dty
la source
4

Je sais que cette question date de quelques années, mais comme Java 8 nous a, entre-temps, amené Optional, j'ai pensé proposer une solution en l'utilisant (et Streamet Collectors):

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  // DLT_UNKNOWN(-1); // <--- NO LONGER NEEDED

  private final int value;
  private PcapLinkType(int value) { this.value = value; }

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static Optional<PcapLinkType> fromInt(int value) {
    return Optional.ofNullable(map.get(value));
  }
}

Optionalest comme null: il représente un cas où il n'y a pas de valeur (valide). Mais c'est une alternative plus sûre pour le type nullou une valeur par défaut, par exemple DLT_UNKNOWNparce que vous pourriez oublier de vérifier les cas nullou DLT_UNKNOWN. Ce sont deux PcapLinkTypevaleurs valides ! En revanche, vous ne pouvez pas affecter une Optional<PcapLinkType>valeur à une variable de type PcapLinkType. Optionalvous fait vérifier d'abord une valeur valide.

Bien sûr, si vous souhaitez conserver DLT_UNKNOWNpour compatibilité descendante ou pour toute autre raison, vous pouvez toujours utiliser Optionalmême dans ce cas, en utilisant orElse()pour le spécifier comme valeur par défaut:

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);

  private final int value;
  private PcapLinkType(int value) { this.value = value; }

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static PcapLinkType fromInt(int value) {
    return Optional.ofNullable(map.get(value)).orElse(DLT_UNKNOWN);
  }
}
Brad Collins
la source
3

Vous pouvez ajouter une méthode statique dans votre énumération qui accepte un intcomme paramètre et renvoie un PcapLinkType.

public static PcapLinkType of(int linkType) {

    switch (linkType) {
        case -1: return DLT_UNKNOWN
        case 0: return DLT_NULL;

        //ETC....

        default: return null;

    }
}
Buhake Sindi
la source
Mieux vaut ne pas oublier d'ajouter une entrée à cette switchinstruction si vous ajoutez une nouvelle énumération. Pas idéal, à mon humble avis.
dty
1
@dty Donc, vous pensez que la surcharge d'un HashMap l'emporte sur la nécessité d'ajouter un nouveau cas à une instruction switch?
écraser
1
Je pense que je préfère écrire du code qui m'aide à ne pas faire d'erreurs et qui est donc plus susceptible d'être correct avant de me concentrer sur les micro-performances d'une recherche de hachage.
dty
3

Voici ce que j'utilise:

public enum Quality {ENOUGH,BETTER,BEST;
                     private static final int amount = EnumSet.allOf(Quality.class).size();
                     private static Quality[] val = new Quality[amount];
                     static{ for(Quality q:EnumSet.allOf(Quality.class)){ val[q.ordinal()]=q; } }
                     public static Quality fromInt(int i) { return val[i]; }
                     public Quality next() { return fromInt((ordinal()+1)%amount); }
                    }
18446744073709551615
la source
L'utilisation d'ordinal a été identifiée comme une mauvaise pratique, en général, il vaut mieux éviter.
Rafael
1
static final PcapLinkType[] values  = { DLT_NULL, DLT_EN10MB, DLT_EN3MB, null ...}    

...

public static PcapLinkType  getPcapLinkTypeForInt(int num){    
    try{    
       return values[int];    
    }catch(ArrayIndexOutOfBoundsException e){    
       return DLT_UKNOWN;    
    }    
}    
nsfyn55
la source
1
Cher si souvent appelé. Vous devez vous rappeler de mettre à jour le tableau (pourquoi l'avez-vous même lorsque les énumérations définissent une .values()méthode?).
dty
@dty est-ce le try / catch? Je pense qu'il serait plus juste de dire que c'est cher si beaucoup de valeurs tombent dans la catégorie DLT_UNKNOWN.
nsfyn55
1
Je suis vraiment surpris de voir une solution de tableau rejetée et une solution de carte votée à la hausse. Ce que je n'aime pas ici, c'est --int, mais c'est évidemment une faute de frappe.
18446744073709551615
Je vois: ils veulent nullà la place de DLT_UKNOWN:)
18446744073709551615
1
Pourquoi pas static final values[] = PcapLinkType.values()?
18446744073709551615
0

Il n'existe aucun moyen de gérer élégamment les types énumérés basés sur des entiers. Vous pourriez envisager d'utiliser une énumération basée sur des chaînes au lieu de votre solution. Ce n'est pas toujours le moyen préféré, mais il existe toujours.

public enum Port {
  /**
   * The default port for the push server.
   */
  DEFAULT("443"),

  /**
   * The alternative port that can be used to bypass firewall checks
   * made to the default <i>HTTPS</i> port.
   */
  ALTERNATIVE("2197");

  private final String portString;

  Port(final String portString) {
    this.portString = portString;
  }

  /**
   * Returns the port for given {@link Port} enumeration value.
   * @return The port of the push server host.
   */
  public Integer toInteger() {
    return Integer.parseInt(portString);
  }
}
Buğra Ekuklu
la source