Lavorare con le Enums

In programmazione, capita molto spesso di avere a che fare con tipi di dati con un definito set di valori, una enumeration è come una lista finita di costanti.

In java una enum è una classe che rappresenta una enumerazione, molto più potente di una lista di costanti perchè è type-safe, con costanti numeriche, potresti passare un valore non compreso e accorgerti di questo solo a runtime, utilizzando le enum ti sarebbe impossibile passare un valore non definito, incorreresti in un errore di compilazione.

L’esempio classico di una enum sono i giorni della settimana, e mesi dell’anno o i pianeti del sistema solare.

Per creare una enumerazione in java si utilizza la parola chiave “enum” al posto di “class” seguita da un elenco di valori:

public enum Stagione {
  INVERNO, AUTUNNO, PRIMAVERA, ESTATE
}

Visto che si tratta di costanti, per convezione i valori devono essere scritti tutti in maiuscolo.

Dietro le quinte, una enum viene trasformata in una classe con variabili statiche e vengono forniti metodi di utilità come name(), usare una enum è molto semplice :

Stagione s = Stagione.INVERNO;
System.out.println(Stagione.ESTATE); // ESTATE
System.out.println(s == Stagione.INVERNO); // true

Come puoi vedere, una enum stampa in nome quando viene invocato il toString e può essere confrontata tramite l’operatore unario ==

Una enum ti fornisce anche un metodo per scorrere tutti i suoi valori, puoi usarla come un array all’interno di un ciclo :

for(Stagione s : Stagione.values()) {
  System.out.println(s.name() + " " + s.ordinal());
}

Come puoi vedere, questo codice stampa i valori della enum seguiti dal loro numero ordinale, crescente in base a come sono dichiarati all’interno della enum, questo valore ordinale resta tale per tutta la durata del programma :

INVERNO 0
AUTUNNO 1
PRIMAVERA 2
ESTATE 3

Ma ricorda, non puoi confontare una enum con un int :

if (Stagione.ESTATE == 2) //ERRORE DI COMPILAZIONE

Per compatibilità con codice vecchio, puoi creare una enum utilizzando una stringa, ma la stringa deve essere perfettamente uguale ad un valore della enum altrimenti prenderai una exception:

Stagione s1 = Stagione.valueOf("INVERNO"); //INVERNO
Stagione s2 = Stagione.valueOf("inverno"); //IllegalArgumentException

Ovviamente, non  essendo propriamente una classe, le enum non possono essere estese :

public enum StagioneEstesa extends Stagione //ERRORE DI COMPILAZIONE

Uso delle enum nel costrutto switch

Le enum possono essere utilizzare nei costrutti switch, rendono il codice decisamente più leggibile:

Stagione s = Stagione.AUTUNNO;
switch (s) {
  case INVERNO:
    System.out.println("fa freddo");
    break;
  case AUTUNNO:
    System.out.println("cadono le foglie");
    break;
  default:
    System.out.println("forse è estate o primavera ?");
    break;
}

Questo codice stampera : cadono le foglie, perchè incontra il valore AUTUNNO definito nello switch.
Fai attenzione, nei case abbiamo scritto solo AUTUNNO, INVERNO e non Stagione.AUTUNNO, Stagione.INVERNO, questo perchè il compilatore Java è abbastanza intelligente da capire che si sta parlando della enum Stagioni, e lo capisce dallo switch.

Ricorda sempre che anche se i valori possiedono un ordinale di tipo int, non puoi utilizzarli nei costrutti switch :

Stagione s = Stagione.ESTATE;
switch (s) {
  case 0:
    System.out.println("fa freddo");
    break;
  default:
    System.out.println("boh?");
    break;
}

Questo codice NON COMPILA !

Aggiunta di costruttori, metodi e campi

Le enum non sono solo fatte di costanti atomiche, puoi definire anche altri valori da associare ad un valore della enums, prendiamo come esempio la enum di prima e vogliamo aggiungergli l’indicazione della temperatura percepita

public enum Stagione {
  INVERNO("freddo"), AUTUNNO("freddino"), PRIMAVERA("si sta bene"), ESTATE("caldo");
  private String temperaturaPercepita;
  private Stagione(String p) {
    this.temperaturaPercepita = p;
  }
  public void comeMiSento() {
    System.out.println(this.temperaturaPercepita);
  }
}

Ci sono molte cose da vedere in questa nuova enum, iniziamo notando il ; alla fine della lista dei valori possibili, questo è richiesto quando i valori delle enum utilizzano un costruttore, se utilizzi solo valori simbolici, puoi omettere il ;

Il resto è normale codice Java, siccome abbiamo passato un valore stringa per ogni valore della enum, abbiamo bisogno di definire un costruttore che accetti una stringa come parametro, questo costruttore deve essere private e non public poichè le enum non possono essere istanziate.

L’invocazione del metodo comeMiSento è molto semplice :

Stagione.ESTATE.comeMiSento(); //caldo

Una nota sui costruttori, tieni a mente che il costruttore della enum viene invocato una sola volta quando viene utilizzata per la prima volta la enum , vediamo un esempio :

public enum SoloUno {
  VALORE(true);
  private SoloUno(boolean b) {
    System.out.println("costruttore invocato");
  }
  public static void main(String... args) {
    SoloUno s1 = SoloUno.VALORE; //costruttore invocato
    SoloUno s1 = SoloUno.VALORE; //non stampa nulla ! costruttore già invocato
  }
}

Questa tecnica ti permette di combinare logica con valori di una enum, ma oltre a definire altre property, puoi anche specificare della logica applicativa all’interno di un valore della enum

Immaginiamo di voler indicare l’ora in cui sorge e tramonta il sole nella nostra enum :

public enum Stagione {
  INVERNO {
    public void stampa(System.out.println("sorge alle 6:40 e tramonta alle 17:30"));
  }, AUTUNNO {
    public void stampa(System.out.println("sorge alle 06:00 e tramonta alle 18:00"));
  }, PRIMAVERA {
    public void stampa(System.out.println("sorge alle 05:00 e tramonta alle 19:00"));
  }, ESTATE {
    public void stampa(System.out.println("sorge alle 04:00 e tramonta alle 20:00"));
  }
  public abstract void stampa();
}

Vediamo che abbiamo creato un metodo astratto stampa(), questo significa che ogni valore della enum deve overridare questo metodo, se ce ne dimentichiamo, otterremo un errore di compilazione, naturalmente, possiamo avere quanti metodi astratti vogliamo.

Possiamo però fare di più, potrebbe essere noioso dover overridare un metodo per ogni valore della enum, immaginiamo di voler avere un comportamento particolare solo per alcuni valori della enum, per tutti gli altri seguire un comportamento di default, per far questo, ci basta togliere la definizione abstract e fornire un implementazione di default :

public enum Stagione {
 INVERNO {
 public void stampa(System.out.println("sorge alle 6:40 e tramonta alle 17:30"));
 }, AUTUNNO {
 public void stampa(System.out.println("sorge alle 06:00 e tramonta alle 18:00"));
 }, PRIMAVERA, ESTATE;
 public void stampa() { System.out.println("sorge quando gli va");
}

Qui vediamo che solo INVERNO e AUTUNNO ridefiniscono il metodo, ESTATE e PRIMAVERA utilizzano quello di default