Va bene usare == sulle enumerazioni in Java?


111

Va bene usare ==su enumerazioni in Java o devo usarlo .equals()? Nei miei test, ==funziona sempre, ma non sono sicuro di averlo garantito. In particolare, non esiste un .clone()metodo su un enum, quindi non so se sia possibile ottenere un enum per il quale .equals()restituirebbe un valore diverso da ==.

Ad esempio, va bene:

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

O devo scriverlo in questo modo:

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


@assylias questa domanda è arrivata prima. Forse bandiera per ♦ attenzione, dal momento che non sono proprio sicuro se i due debbano essere fusi.
Matt Ball

@ MattBall Penso che la risposta alla tua domanda che cita il JLS sia la risposta migliore, motivo per cui ho scelto di chiudere questa.
assylias

Risposte:


149

Solo i miei 2 centesimi: ecco il codice per Enum.java, come pubblicato da Sun, e parte del 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
Grazie! Immagino che se avessi pensato di passare a .equals () con il compilatore avrei visto questo ...
Kip

77

Sì, == va bene - è garantito che ci sia solo un singolo riferimento per ogni valore.

Tuttavia, c'è un modo migliore per scrivere il tuo metodo rotondo:

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

Un modo ancora migliore per farlo è inserire la funzionalità all'interno dell'enumerazione stessa, quindi puoi semplicemente chiamare roundingMode.round(someValue). Questo arriva al cuore delle enumerazioni Java: sono enumerazioni orientate agli oggetti, a differenza dei "valori denominati" trovati altrove.

EDIT: le specifiche non sono molto chiare, ma la sezione 8.9 afferma:

Il corpo di un tipo enum può contenere costanti enum. Una costante enum definisce un'istanza del tipo enum. Un tipo enum non ha istanze diverse da quelle definite dalle sue costanti enum.


Mi piacerebbe crederti sulla parola, ma se potessi fornire un collegamento a qualche documentazione ufficiale sarebbe meglio ...
Kip

switch non è utile quando c'è molta sovrapposizione tra i diversi casi. Inoltre, RoundingMode fa parte di java.math, quindi non posso aggiungere un metodo.
Kip

2
Oh ... e dubiti di Jon Skeet? Non sei qui da molto tempo;)
Joel Coehoorn

enumerazioni nelle istruzioni switch? Non sapevo che fosse possibile. Dovrò provarlo un giorno.
luiscubal

Incapsulare la logica all'interno delle enumerazioni utilizzando metodi astratti è il vero potere delle enumerazioni. Rende il tuo codice molto più robusto; quando aggiungi un nuovo valore enum in futuro, il compilatore ti costringerà a implementare la logica pertinente, non devi ricordarti di aggiungere un caso a più istruzioni switch.
Andrew Swan

13

Sì, è come se avessi creato istanze singleton per ogni valore nell'enum:

public abstract class RoundingMode {
  finale statico pubblico RoundingMode HALF_UP = new RoundingMode ();
  finale statico pubblico RoundingMode HALF_EVEN = new RoundingMode ();

  RoundingMode privato () {
    // l'ambito privato impedisce qualsiasi sottotipo al di fuori di questa classe
  }
}

Tuttavia , il enumcostrutto ti offre vari vantaggi:

  • Il toString () di ogni istanza stampa il nome dato nel codice.
  • (Come accennato in un altro post,) una variabile di tipo enum può essere confrontata con le costanti utilizzando la switch-casestruttura di controllo.
  • Tutti i valori nell'enumerazione possono essere interrogati utilizzando il valuescampo "generato" per ogni tipo di enumerazione
  • Ecco il grande problema dei confronti di identità: i valori enum sopravvivono alla serializzazione senza clonazione.

La serializzazione è un grande gotchya. Se dovessi usare il codice sopra invece di un enum, ecco come si comporterebbe l'uguaglianza di identità:

RoundingMode original = RoundingMode.HALF_UP;
asserire (RoundingMode.HALF_UP == originale); // passa

ByteArrayOutputStream baos = nuovo ByteArrayOutputStream ();
ObjectOutputStream oos = nuovo ObjectOutputStream (baos);
oos.writeObject (originale);
oos.flush ();

ByteArrayInputStream bais = nuovo ByteArrayInputStream (baos.toByteArray ());
ObjectInputStream ois = nuovo ObjectInputStream (bais);
RoundingMode deserialized = (RoundingMode) ois.readObject ();

asserire (RoundingMode.HALF_UP == deserializzato); // non riesce
asserire (RoundingMode.HALF_EVEN == deserializzato); // non riesce

È possibile risolvere questo problema senza enum, utilizzando una tecnica che coinvolge writeReplacee readResolve, (vedere http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Immagino che il punto sia: Java fa di tutto per consentirti di utilizzare le identità dei valori enum per testare l'uguaglianza; è una pratica incoraggiata.


1
Il bug di serializzazione è stato corretto. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.

@DavidI. grazie per l'aggiornamento. Questo è un bug molto inquietante e buono a sapersi!
Dilum Ranatunga

1
@DilumRanatunga All'inizio pensavo che questo mi avrebbe influenzato, ma sembra che funzionino bene dopo averli trasmessi tramite una connessione RMI.
David I.


6

Ecco un codice malvagio che potresti trovare interessante. : D

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 puoi includere le importazioni di questo codice? Cound non trovare Unsafe.class.
rumman0786

3

Gli enum sono un ottimo posto per inceppare il codice polimorfico.

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
Sì, ma non possiedo java.math.RoundingMode, quindi non posso farlo nel mio caso.
Kip


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.