Meccanismi avanzati: enumerazioni

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

versione stampabile

Riconoscimenti

  • Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.

  • Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.

Outline

Goal della lezione

  • Illustrare il concetto di enumerazione e le sue implementazioni in Java

Enumerazioni

Motivazioni

  • in alcune situazioni occorre definire dei tipi che possono assumere solo un numero fissato e limitato di possibili valori
  • Esempi:
    • le regioni d’Italia, il sesso di un individuo, i 6 pezzi negli scacchi, i giorni della settimana, le tipologie di camere di un hotel, le scuole di un ateneo, eccetera

Possibili realizzazioni in Java

  • usare delle String rappresentando il loro nome: quasi assurdo
  • usare degli int per codificarli (come in C): poco leggibile
  • usare delle classi astratte, e una concreta per valore: prolisso

Enumerazioni: enum E { ... }

  • consentono di elencare i valori, associando ad ognuno un nome
  • è possibile collegare metodi e campi ad ogni “valore”

Esempio classe Persona: 1/2

public class Persona {
	private final String nome;
	private final String cognome;
	private final String regione;

	public Persona(String nome, String cognome, String regione) {
		this.nome = nome;
		this.cognome = cognome;
		this.regione = regione;
	}

	public String toString() {
		return "[" + nome + "," + cognome + "," + regione + "]";
	}

Esempio classe Persona: 2/2


	public boolean isIsolano() { // Confronto molto lento!!
		return (this.regione.equals("Sardegna") || 
				this.regione.equals("Sicilia"));
	}

	public static List<Persona> fromRegione(
			final Collection<Persona> coll, 
			final String regione
	) { // nota questa possibile formattazione 
		final List<Persona> list = new ArrayList<>();
		for (final Persona persona : coll) {
			if (persona.regione.equals(regione)) { // Confronto lento!!
				list.add(persona);
			}
		}
		return list;
	}
}

UsePersona

public class UsePersona {
	public static void main(String[] args){
		final ArrayList<Persona> list = new ArrayList<>();
		list.add(new Persona("Mario","Rossi","Emilia-Romagna"));
		list.add(new Persona("Gino","Bianchi","Sicilia"));
		list.add(new Persona("Carlo","Verdi","EmiliaRomagna")); 
		// Errore sul nome non intercettabile
		final List<Persona> out = Persona.fromRegione(list,"Emilia-Romagna");
		System.out.println(list);
		// [[Mario,Rossi,Emilia-Romagna], [Gino,Bianchi,Sicilia], 
		//    [Carlo,Verdi,EmiliaRomagna]]
		System.out.println(out);
		// [[Mario,Rossi,Emilia-Romagna]]
		for (final Persona p: list){
			if (p.isIsolano()){
				System.out.println(p);
			}
		}
		// [Gino,Bianchi,Sicilia]
	}
}

Soluzione alternativa, Persona: 1/2

public class Persona {
    
    public static final int LOMBARDIA = 0;
    public static final int EMILIA_ROMAGNA = 1;
    public static final int SICILIA = 2;
    public static final int SARDEGNA = 3;
    ...
    
    private final String nome;
    private final String cognome;
    private final int regione;
	
    public Persona(String nome, String cognome, int regione) {
    	this.nome = nome;
    	this.cognome = cognome;
    	this.regione = regione;
    }
	
    private static String nomeRegione(int regione){
        switch (regione){
    	    case 0: return "Lombardia";
    	    case 1: return "Emilia-Romagna";
    	    ...
        }
    } 

Soluzione alternativa Persona: 2/2

	public String toString() {
		return "[" + nome + "," + cognome + ","	+ nomeRegione(regione) + "]";
	}

	public boolean isIsolano(){
		// Confronto veloce!!
		return (this.regione == SARDEGNA || 
		        this.regione == SICILIA);
	}

	public static List<Persona> fromRegione(Collection<Persona> coll, 
	                           String regione){
		ArrayList<Persona> list = new ArrayList<>();
		for (Persona persona: coll){
		    // Confronto veloce!!
			if (persona.regione == regione){
				list.add(persona);
			}
		}
		return list;
	}
}

Soluzione alternativa UsePersona

import java.util.ArrayList;
import java.util.List;

public class UsePersona {
    public static void main(String[] args){
        ArrayList<Persona> list = new ArrayList<>();
        list.add(new Persona("Mario","Rossi",Persona.EMILIA_ROMAGNA));
        list.add(new Persona("Gino","Bianchi",3));
        list.add(new Persona("Carlo","Verdi",Persona.LOMBARDIA)); 
        // Non si hanno errori sulle stringhe
        // Non si può prevenire l'uso diretto di interi..
        ...
    }
}

Discussione

Approccio a stringhe

  • Penalizza molto le performance spazio-tempo
  • Può comportare errori gravi per scorrette digitazioni
  • Difficile intercettare gli errori

Approccio a interi – soluzione pre-enumerazioni

  • Buone performance ma cattiva leggibilità
  • Può comportare comunque errori, anche se più difficilmente
  • L’uso delle costante è un poco dispersivo

Altri approcci: uso di classi diverse per ogni valore

  • Impraticabile con un numero molto elevato di valori
  • Comunque molto prolisso in termini di quantità di codice
  • Tuttavia:
    • Previene gli errori che si possono commettere
    • Consente facilmente di aggiungere nuovi elementi

Soluzione via costanti e costruttore privato

public class Regione {
	public static final Regione MARCHE = new Regione(0,"Marche");
	public static final Regione VENETO = new Regione(1,"Veneto");
	public static final Regione LOMBARDIA = new Regione(2,"Lombardia");
	
	public static final Regione[] VALUES =  new Regione[] {MARCHE, VENETO, LOMBARDIA};
	
	private final int id;
	private final String name;
	
	private Regione(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return this.id;
	}

	public String getName() {
		return this.name;
	}

	public String toString() {
		return "Regione [id=" + id + ", name=" + name + "]";
	}
}

UseRegione

import java.util.Arrays;

public class UseRegione {
	public static void main(String[] args) {
		// nella variabile regione, si possono usare solo 3 casi
		Regione regione = Regione.MARCHE;
			
		System.out.println(regione);
		// si ottengono gli array dei valori possibile
		System.out.println(Arrays.toString(Regione.VALUES));
		// è possibile accedere alla "prossima regione"
		System.out.println(Regione.VALUES[regione.getId()+1]);
	}
}

enum in Java

Un nuovo tipo di dato

  • Simile ad una classe
  • Realizza l’approccio a costanti e costruttore privato
  • Ottime performance, l’oggetto è già disponibile
  • Impedisce interamente errori di programmazione
  • Il codice aggiuntivo da produrre non è elevato

Unica precauzione

  • Andrebbero usate per insiemi di valori che difficilmente cambieranno in futuro
    • Difficile modificare il codice successivamente (!)

enum Regione

public enum Regione {
	ABRUZZO, BASILICATA, CALABRIA, CAMPANIA, EMILIA_ROMAGNA, 
	FRIULI_VENEZIA_GIULIA, LAZIO, LIGURIA, LOMBARDIA, MARCHE, 
	MOLISE, PIEMONTE, PUGLIA, SARDEGNA, SICILIA, TOSCANA,
	TRENTINO_ALTO_ADIGE, UMBRIA, VALLE_D_AOSTA,	VENETO;
}
import java.util.*;

public class UseEnum {
	public static void main(String[] args) {
		final List<Regione> list = new ArrayList<>();
		
		list.add(Regione.LOMBARDIA);
		list.add(Regione.PIEMONTE);
		list.add(Regione.EMILIA_ROMAGNA);
		
		for (final Regione r: list){
			System.out.println(r.toString());
		}
	}
}

Persona con uso della enum

public class Persona {
	private final String nome;
	private final String cognome;
	private final Regione regione;
	
	public Persona(String nome, String cognome, Regione regione) {
		this.nome = nome;
		this.cognome = cognome;
		this.regione = regione;
	}
	
	public boolean isIsolano(){
		// Confronto veloce!!
		return (this.regione == Regione.SARDEGNA 
				|| this.regione == Regione.SICILIA);
	}

	public static List<Persona> fromRegione(Collection<Persona> coll, 
			Regione regione){
		final List<Persona> list = new ArrayList<>();
		for (final Persona persona: coll){
			if (persona.regione == regione){ // Nota l'== !!
				list.add(persona);
			}
		}
		return list;
	}
	
	public String toString() {
		return "[" + nome + "," + cognome + ","	+ regione + "]";
	}
}

UsePersona con uso della enum

import java.util.*;

public class UsePersona {
	public static void main(String[] args){
		final ArrayList<Persona> list = new ArrayList<>();
		list.add(new Persona("Mario","Rossi",Regione.EMILIA_ROMAGNA));
		list.add(new Persona("Gino","Bianchi",Regione.SICILIA));
		list.add(new Persona("Carlo","Verdi",Regione.LOMBARDIA));
		final List<Persona> out = 
		    Persona.fromRegione(list,Regione.EMILIA_ROMAGNA); 
		System.out.println(list);
		// [[Mario,Rossi,EMILIA_ROMAGNA], [Gino,Bianchi,SICILIA], 
		// [Carlo,Verdi,LOMBARDIA]]
		System.out.println(out);
		// [[Mario,Rossi,EMILIA_ROMAGNA]]
		for (final Persona p: list){
			if (p.isIsolano()){
				System.out.println(p);
			}
		}
		// [Gino,Bianchi,SICILIA]
	}
}

UsePersona con import static

import static it.unibo.advancedmechanisms.enums.en2.Regione.*;

import java.util.*;

public class UsePersona2 {
	public static void main(String[] args){
		final ArrayList<Persona> list = new ArrayList<>();
		list.add(new Persona("Mario", "Rossi", EMILIA_ROMAGNA));
		list.add(new Persona("Gino", "Bianchi", SICILIA));
		list.add(new Persona("Carlo", "Verdi", LOMBARDIA));
		final List<Persona> out = Persona.fromRegione(list, EMILIA_ROMAGNA); 
		System.out.println(list);
		// [[Mario,Rossi,EMILIA_ROMAGNA], [Gino,Bianchi,SICILIA], 
		// [Carlo,Verdi,LOMBARDIA]]
		System.out.println(out);
		// [[Mario,Rossi,EMILIA_ROMAGNA]]
		for (final Persona p: list){
			if (p.isIsolano()){
				System.out.println(p);
			}
		}
		// [Gino,Bianchi,SICILIA]
	}
}

Metodi di default per ogni enum

import static it.unibo.advancedmechanisms.enums.en2.Regione.*;

import java.util.*;


public class UseRegione {
	public static void main(String[] args) {
		final ArrayList<Regione> list = new ArrayList<>();
		// 4 modi di ottenere una Regione
		list.add(Regione.LOMBARDIA);
		list.add(SARDEGNA);
		list.add(Regione.valueOf("SICILIA"));
		list.add(Regione.values()[10]);
		
		for (final Regione r: list){
			System.out.println("toString "+r); // LOMBARDIA,...,MOLISE
			System.out.println("ordinale "+r.ordinal()); // 8, 13, 14, 10
			System.out.println("nome "+r.name()); // LOMBARDIA,...,MOLISE
			System.out.println("---");
		}
		
		for (final Regione r: Regione.values()){
			System.out.print(r+" "); // Stampa tutte le regioni
		}

	}
}

enum negli switch

import static it.unibo.advancedmechanisms.enums.en2.Regione.*;

import java.util.*;

public class UseRegione2 {
	public static void main(String[] args) {
		final ArrayList<Regione> list = new ArrayList<>();
		// 4 modi di ottenere una Regione
		list.add(Regione.LOMBARDIA);
		list.add(SARDEGNA);
		list.add(Regione.valueOf("SICILIA"));
		list.add(Regione.values()[10]);

		// Le enum sono usabili negli switch
		for (final Regione r : list) {
			switch (r) {
			case LOMBARDIA:
				System.out.println("Lombardia");
				break;
			case EMILIA_ROMAGNA:
				System.out.println("Emilia Romagna");
				break;
			default:
				System.out.println("Altre..");
			}
		}
	}
}

Metodi aggiuntivi nelle enum: Zona

import java.util.*;

public enum Zona {
	NORD, CENTRO, SUD;
	
	public List<Regione> getRegioni(){
		final ArrayList<Regione> list=new ArrayList<>();
		for (final Regione r: Regione.values()){
			if (r.getZona()==this){
				list.add(r);
			}
		}
		return list;
	}
}

Metodi e campi aggiuntivi nelle enum: Regione (1/2)

import static it.unibo.advancedmechanisms.enums.en3.Zona.*;

public enum Regione {
	ABRUZZO(CENTRO,"Abruzzo"), 
	BASILICATA(SUD,"Basilicata"), 
	CALABRIA(SUD,"Calabria"), 
	CAMPANIA(SUD,"Campania"), 
	EMILIA_ROMAGNA(NORD, "Emilia Romagna"), 
	FRIULI_VENEZIA_GIULIA(NORD, "Friuli Venezia Giula"), 
	LAZIO(CENTRO, "Lazio"), 
	LIGURIA(NORD, "Liguria"), 
	LOMBARDIA(NORD, "Lombardia"), 
	MARCHE(CENTRO, "Marche"),
	MOLISE(SUD, "Molise"), 
	PIEMONTE(NORD, "Piemonte"), 
	PUGLIA(SUD, "Puglia"), 
	SARDEGNA(SUD, "Sardegna"), 
	SICILIA(SUD, "Sicilia"), 
	TOSCANA(CENTRO, "Toscana"),
	TRENTINO_ALTO_ADIGE(NORD, "Trentino Alto Adige"), 
	UMBRIA(CENTRO, "Umbria"), 
	VALLE_D_AOSTA(NORD,"Valle D'Aosta"),
	VENETO(NORD,"Veneto");
	
	private final Zona z;
	private final String actualName;
	
	private Regione(final Zona z, final String actualName){
		this.z = z;
		this.actualName = actualName;
	}
	
	public Zona getZona(){
		return this.z;
	}
	
	public String toString(){
		return this.actualName;
	}
}

Metodi e campi aggiuntivi nelle enum: Regione (2/2)

	SICILIA(SUD, "Sicilia"), 
	TOSCANA(CENTRO, "Toscana"),
	TRENTINO_ALTO_ADIGE(NORD, "Trentino Alto Adige"), 
	UMBRIA(CENTRO, "Umbria"), 
	VALLE_D_AOSTA(NORD,"Valle D'Aosta"),
	VENETO(NORD,"Veneto");
	
	private final Zona z;
	private final String actualName;
	
	private Regione(final Zona z, final String actualName){
		this.z = z;
		this.actualName = actualName;
	}
	
	public Zona getZona(){
		return this.z;
	}
	
	public String toString(){
		return this.actualName;
	}
}

Metodi e campi aggiuntivi nelle enum: UseZona

import static it.unibo.advancedmechanisms.enums.en3.Zona.*; 

public class UseZona {
	public static void main(String[] args) {
		for (Regione r: NORD.getRegioni()){
			System.out.println("toString "+r); 
			// Emilia Romagna,...,Veneto
			System.out.println("nome "+r.name());
			// EMILIA_ROMAGNA,...,VENETO
			System.out.println("---");
		}
	}
}

Meccanismi per le enum

Riassunto

  • Esistono metodi istanza e statici disponibili per Enum
  • Si possono aggiungere metodi
  • Si possono aggiungere campi e costruttori

Riguardando la enum Regione

  • È una classe standard, con l’indicazioni di alcuni oggetti predefiniti
  • I 20 oggetti corrispondenti alle regioni italiane

Quindi

  • È possibile intuirne la realizzazione interna
  • E quindi capire meglio quando e come usarli
  • $\Rightarrow$ In caso in cui i valori sono “molti e sono noti”, oppure..
  • $\Rightarrow$ Anche se i valori sono pochi, ma senza aggiungere troppi altri metodi..

enum innestate

Motivazione

  • Anche le enum (statiche) possono essere innestate in una classe o interfaccia o enum
  • Questo è utile quando il loro uso è reputato essere confinato nel funzionamento della classe outer

Esempio

  • enum Regione potrebbe essere inserita dentro Persona
  • enum Zona potrebbe essere inserita dentro Regione