Est-il acceptable d'utiliser == sur les énumérations en Java?


111

Puis-je utiliser des ==énumérations en Java ou dois-je utiliser .equals()? Dans mes tests, cela ==fonctionne toujours, mais je ne suis pas sûr que j'en soit garanti. En particulier, il n'y a pas de .clone()méthode sur une enum, donc je ne sais pas s'il est possible d'obtenir une enum pour laquelle .equals()retournerait une valeur différente de ==.

Par exemple, est-ce OK:

public int round(RoundingMode roundingMode) {
  if(roundingMode == RoundingMode.HALF_UP) {
    //do something
  } else if (roundingMode == RoundingMode.HALF_EVEN) {
    //do something
  }
  //etc
}

Ou dois-je l'écrire de cette façon:

public int round(RoundingMode roundingMode) {
  if(roundingMode.equals(RoundingMode.HALF_UP)) {
    //do something
  } else if (roundingMode.equals(RoundingMode.HALF_EVEN)) {
    //do something
  }
  //etc
}


@assylias cette question est venue en premier. Peut-être attirer l'attention ♦, car je ne sais pas vraiment si les deux devraient être fusionnés.
Matt Ball

@MattBall Je pense que la réponse à votre question qui cite le JLS est la meilleure réponse, c'est pourquoi j'ai choisi de fermer celle-ci.
assylias

Réponses:


149

Juste mes 2 cents: Voici le code pour Enum.java, tel que publié par Sun, et faisant partie du JDK:

public abstract class Enum<E extends Enum<E>>
    implements Comparable<E>, Serializable {

    // [...]

    /**
     * Returns true if the specified object is equal to this
     * enum constant.
     *
     * @param other the object to be compared for equality with this object.
     * @return  true if the specified object is equal to this
     *          enum constant.
     */
    public final boolean equals(Object other) { 
        return this==other;
    }


}

4
Merci! Je suppose que si j'avais juste pensé à entrer dans .equals () avec le compilateur, j'aurais vu ça ...
Kip le

77

Oui, == est très bien - il n'y a qu'une seule référence pour chaque valeur.

Cependant, il existe une meilleure façon d'écrire votre méthode ronde:

public int round(RoundingMode roundingMode) {
  switch (roundingMode) {
    case HALF_UP:
       //do something
       break;
    case HALF_EVEN:
       //do something
       break;
    // etc
  }
}

Une façon encore meilleure de le faire est de mettre la fonctionnalité dans l'énumération elle-même, afin que vous puissiez simplement appeler roundingMode.round(someValue). Cela touche le cœur des énumérations Java - ce sont des énumérations orientées objet , contrairement aux «valeurs nommées» trouvées ailleurs.

EDIT: La spécification n'est pas très claire, mais la section 8.9 stipule:

Le corps d'un type enum peut contenir des constantes enum. Une constante enum définit une instance du type enum. Un type enum n'a pas d'instances autres que celles définies par ses constantes enum.


Je serais ravi de vous croire sur parole, mais si vous pouviez fournir un lien vers une documentation officielle qui serait mieux ...
Kip

switch n'est pas utile lorsqu'il y a beaucoup de chevauchement entre différents cas. De plus, RoundingMode fait partie de java.math, je ne peux donc pas y ajouter de méthode.
Kip le

2
Oh ... et vous doutez de Jon Skeet? Vous n'êtes pas ici depuis très longtemps;)
Joel Coehoorn

enums dans les instructions switch? Je ne savais pas que c'était possible. Je vais devoir l'essayer un jour.
luiscubal

Encapsuler la logique dans les énumérations à l'aide de méthodes abstraites est le véritable pouvoir des énumérations. Cela rend votre code beaucoup plus robuste; lorsque vous ajoutez une nouvelle valeur d'énumération à l'avenir, le compilateur vous obligera à implémenter la logique appropriée, vous n'avez pas à vous rappeler d'ajouter une casse à plusieurs instructions switch.
Andrew Swan

13

Oui, c'est comme si vous aviez créé des instances singleton pour chaque valeur de l'énumération:

classe abstraite publique RoundingMode {
  public static final RoundingMode HALF_UP = new RoundingMode ();
  public static final RoundingMode HALF_EVEN = new RoundingMode ();

  private RoundingMode () {
    // la portée privée empêche tout sous-type en dehors de cette classe
  }
}

Cependant , la enumconception vous offre divers avantages:

  • Le toString () de chaque instance imprime le nom donné dans le code.
  • (Comme mentionné dans un autre article,) une variable de type enum peut être comparée à des constantes en utilisant la switch-casestructure de contrôle.
  • Toutes les valeurs de l'énumération peuvent être interrogées à l'aide du valueschamp «généré» pour chaque type d'énumération
  • Voici la grande comparaison des identités: les valeurs d'énumération survivent à la sérialisation sans clonage.

La sérialisation est un gros gotchya. Si je devais utiliser le code ci-dessus au lieu d'une énumération, voici comment l'égalité d'identité se comporterait:

RoundingMode original = RoundingMode.HALF_UP;
assert (RoundingMode.HALF_UP == original); // passe

ByteArrayOutputStream baos = new ByteArrayOutputStream ();
ObjectOutputStream oos = new ObjectOutputStream (baos);
oos.writeObject (original);
oos.flush ();

ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray ());
ObjectInputStream ois = new ObjectInputStream (bais);
RoundingMode désérialisé = (RoundingMode) ois.readObject ();

assert (RoundingMode.HALF_UP == désérialisé); // échoue
assert (RoundingMode.HALF_EVEN == désérialisé); // échoue

Vous pouvez résoudre ce problème sans énumération, en utilisant une technique qui implique writeReplaceet readResolve, (voir http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Je suppose que le point est - Java fait tout son possible pour vous permettre d'utiliser les identités des valeurs enum pour tester l'égalité; c'est une pratique encouragée.


1
Le bogue de sérialisation a été corrigé. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.

@DavidI. Merci pour la mise à jour. C'est un bug très dérangeant, et bon à savoir!
Dilum Ranatunga

1
@DilumRanatunga Je pensais que cela allait m'affecter au début, mais ils semblent fonctionner correctement après les avoir passés via une connexion RMI.
David I.


6

Voici un code maléfique que vous pourriez trouver intéressant. :RÉ

public enum YesNo {YES, NO}

public static void main(String... args) throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
    YesNo yesNo = (YesNo) unsafe.allocateInstance(YesNo.class);

    Field name = Enum.class.getDeclaredField("name");
    name.setAccessible(true);
    name.set(yesNo, "YES");

    Field ordinal = Enum.class.getDeclaredField("ordinal");
    ordinal.setAccessible(true);
    ordinal.set(yesNo, 0);

    System.out.println("yesNo " + yesNo);
    System.out.println("YesNo.YES.name().equals(yesNo.name()) "+YesNo.YES.name().equals(yesNo.name()));
    System.out.println("YesNo.YES.ordinal() == yesNo.ordinal() "+(YesNo.YES.ordinal() == yesNo.ordinal()));
    System.out.println("YesNo.YES.equals(yesNo) "+YesNo.YES.equals(yesNo));
    System.out.println("YesNo.YES == yesNo " + (YesNo.YES == yesNo));
}

1
@Peter pouvez-vous s'il vous plaît inclure les importations de ce code? Je ne trouve pas Unsafe.class.
rumman0786

3

Les énumérations sont un excellent endroit pour brouiller le code polymorphe.

enum Rounding {
  ROUND_UP {
    public int round(double n) { ...; }
  },
  ROUND_DOWN {
    public int round(double n) { ...; }
  };

  public abstract int round(double n);
}

int foo(Rounding roundMethod) {
  return roundMethod.round(someCalculation());
}

int bar() {
  return foo(Rounding.ROUND_UP);
}

1
Oui mais je ne possède pas java.math.RoundingMode, donc je ne peux pas faire cela dans mon cas.
Kip le


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.