Tutorial Java SCJP – # 19 Enumerari de tip enum

Exista situatii in care o variabila trebuie sa aiba valori limitate la o anumita multime, definita in specificatiile solutiei. Sa presupunem ca trebuie dezvoltata o aplicatie Java care gestioneaza Vehicule iar tipul de motor trebuie sa ia o valoare din multimea {BENZINA, DIESEL, HYBRID, ELECTRIC}. Pentru a implementa cerinta se poate defini atributul asociat tipului de motor ca String sau ca int si se valideaza de fiecare data valoarea de intrare. Pentru siruri de caractere se poate compara valoarea de intrare cu “BENZINA”, “DIESEL”, si asa mai departe. Pentru int se poate face asocierea BENZINA este 1, DIESEL este 2, … si se verifica valorile pe baza acesti abordari. Aceasta este o solutie posibila, dar nu e eficienta pentru ca se pot face cu usurinta greseli si pentru ca se complica o procedura care ar trebui sa fie simpla.

Incepand cu Java 5.0, framework-ul ofera o solutie mai eleganta la aceasta problema, permitand programatorilor sa defineasca un set predefinit de valori, o enumerare sau enum (cuvant cheie in Java). In plus, programatorii pot defini variabile care pot lua valori doar din enumerare, iar compilatorul va valida acest lucru prin generarea de erori in situatiile in care programatorul va incerca sa initializeze variabila cu o valoare din afara enumerarii. Enumerarile (enums) pot fi interpretate ca un nou tip de date, pentru care se defineste o restrictie cu privire la valorile sale.

Alte subiecte care fac parte din acest tutorial Java sunt accesibile prin intermediul Java Tutorial 6 – Cuprins.

Conceptul din spatele enumerarilor nu este ceva nou, deoarece in alte limbaje de programare ca C, C + +, C # exista aceasta facilitate, iar in unele cazuri cu mult inainte sa fie implementata in Java.

Cum se definesc si se utilizeaza enumerarile

Cea mai utilizata sintaxa (in Java enumerarile sunt de fapt clase in care se pot declara metode si atribute) pentru a declara o enumerare este:

enum nume_enumerare {constanta1, constanta2, …, constantaN } ;  // ATENTIE ; (punctul si virgula) de la final este optional

Enumerarile pot fi declarate:

  • independent, la nivel global asemenea unei clase;
  • intr-o alta clasa;
  • NU in metode;

Revenind la scenariul cu vehicule, solutia optima este de a declara o enumerare pentru tipul de motor:

//enumerare definita independent asemenea unei clase
//ATENTIE ! ; de la final este optionala
enum TipMotor {DIESEL,BENZINA,HYBRID,ELECTRIC}

class Vehicul
{
    //enumerare definita in interiorul unei clase
    protected enum TipCulori{RED, GREEN, BLUE, WHITE};

    //atribut instanta  de tip TipMotor
    public TipMotor motor;
    //atribut instanta  de tip TipCulori
    public TipCulori culoare;
}

La definirea enumerarii se are in vedere:

  • simboluri sau constantele din enumerare sunt de obicei definite cu majuscule (cum ar fi DIESEL, BENZINA, …) daca se au in vedere conventiile de nume recomandate de Java;
  • simbolurile din enumerari nu sunt valori int sau String;
  • enumerarile delarate la nivel global pot fi definite DOAR default sau public (si NU private sau protected), dar in acest ultim caz, in propriile lor fisier .java (aceeasi regula vallabila si pentru clase);
  • enumerarile declarate intr-o alta clasa pot avea modificatori de acces (private, public, default, protected) care controleaza vizibilitatea enumerarii in afara clasei parinte (mai multe despre modificatori de acces in Tutorial Java SCJP – # 17 modificatori de acces pentru metode si atribute );

Pe baza descrierii anterioare, enumerarile sunt folosite pentru a controla valorile posibile ale unei variabile. Deci, daca vrem sa initializam atributul motor al instantei trebuie sa ne folosim numai constante din enumerarea TipMotor. Pentru enumerari definite in alte clase programatorul trebuie sa foloseasca numele complet, care include numele clasei parinte.

public class Main {
    public static void main(String[] args) {
        Vehicul v = new Vehicul();
        v.motor = TipMotor.DIESEL;

        //erori compilare:
        //v.motor = "DIESEL";  //eroare
        //v.motor = 1;         //eroare

	//referire completa:  nume_clasa.nume_enumerare.simbol
        v.culoare = Vehicul.TipCulori.GREEN;
        //compiler eroare
        //v.culoare = TipCulori.GREEN; //eroare
    }
}

O caracteristica interesanta a enumerarilor este ca simbolurile sale pot fi usor convertite la o valoare String egala cu numele simbolului. Codul secventa urmatoare:

        System.out.println("The vehicle motor type is "+v.motor);
        
        String motorType = TipMotor.ELECTRIC.toString();
        System.out.println("The motor type is "+motorType);

genereaza mesajul:

The vehicle motor type is DIESEL
The motor type is ELECTRIC

In Java enumerarile sunt clase

In alte limbaje de programare cum ar fi C sau C++, enumerarile sunt colectii de simboluri care au asociat un ID unic numeric. Aceasta descriere nu se poate aplica si aici, pentru ca in Java enumerarile sunt clase. Simbolurile enumerarilor sunt atribute statice si constante ce reprezinta instante ale clasei din spatele enumerarii. Din  aceasta perspectiva putem presupune ca simbolul DIESEL este definit de masina virtuala cu declaratie (aceasta este o presupunere care nu este foarte departe de adevar):

    //expresia NU este valida in JAVA
    //doar pentru a intelege simbolurile din enumerari
    public static final TipMotor DIESEL = new TipMotor("DIESEL");

Deoarece enumerarile sunt clase, inseamna ca avem posibilitatea sa definim in interiorul acesto structuri (in afara de simbolurile sale):

  • variabile de instanta
  • metode de acces sau de prelucrare;
  • constructori.

Daca vrem sa definim in interiorul unei enumerari mai mult decât simbolurile, intelegand prin asta alte valori sau rutine interne de prelucrare, putem atinge acest obiectiv datorita faptului ca structurile de tip enumerare sunt in Java clase.

Revenind la scenariul definit de clasa Vehicul, definim o serie de specificatii care sa justifice prelucrarea enumerarii din punct de vedere al unei clase:

  • se defineste o variabila asociata fiecare tip de motor care va stoca un cod unic numeric;
  • atributul  cu rol de cod este protejat prin definirea acestuia privat;
  • se defineste o metoda care da acces in mod citire la cod pentru a-I afisa valoarea;
  • este nevoie de un constructor, deoarece fiecare simbol TipMotor are propriul cod unic.

Implementand cerintele anterioare, enumerarea TipMotor devine:

//enumerare definita independent
//ATENTIE ; din final este optionala
enum TipMotor{
    //fiecare simbol este creat apeland constructorul enumerarii
    DIESEL(10),BENZINA(20),HYBRID(30),ELECTRIC(40);
    
    //variabila de instanta pentru cod
    private int cod;
    
    //constructor
    TipMotor(int codValue){
        cod = codValue;
    }

    //metoda din enumerare
    public int getCode(){
        return cod;
    }
// terminare enumerare
}

In ciuda faptului ca enumerarea TipMotor arata ca o clasa, aceasta nu este una obisnuita, deoarece:

  • modificatorii de acces public si protected NU sunt permisi pentru constructor;
  • NU se pot crea instante prin apelul direct al constructorului (de exemplu new TipMotor(30));
  • definirea simbolurilor enumerarii trebuie sa reprezinte prima declaratie din clasa;
  • se poate apela connstructorul enumerarii DOAR numai când se definesc simbolurile (de exemplu, DIESEL(10)), chiar daca sintaxa este diferita pentru Java;
  • este posibila supraincarcarea constructorilor din enumerare si definirea lor cu orice numar de parametri;

In acest caz, simboluri din enumerare se comporta ca obiecte si prin intermediul lor se pot apela metode din clasa. Variabilele de tip TipMotor reprezinta referinte la obiecte constante (simbolurile din enumerare). Secventa urmatoare:

        System.out.println("Tipul motorului este " + v.motor +
                " iar codul acestuia este "+v.motor.getCode());

afiseaza:

	Tipul motorului este DIESEL iar codul acestuia este 10

Cum se parcurge lista de simboluri dintr-o enumerare

Fiecare enumerare are o metoda statica, values() , utilizata pentru a itera peste constantele/simbolurile sale. Secventa urmatoare:

        System.out.println("Simbolurile din TipMotor sunt ");
        for(TipMotor et : TipMotor.values())
        {
            System.out.println(et + " cu codul "+et.getCode());
        }

genereaza:

Simbolurile din TipMotor sunt 
DIESEL cu codul 10
BENZINA cu codul 20
HYBRID cu codul 30
ELECTRIC cu codul 40

Ce reprezinta “constant specific class body“

Enumerarile sunt liste de obiecte constante care reprezinta un set limitat de valori. Aceste valori sunt semnificative intr-un context foarte specific.

In ciuda faptulcui ca enumerarile sunt clase, programatorii mentin complexitatea solutie la un nivel simplu, deoarece aceste structuri sunt un tip special de clasa cu un rol foarte specific. Cele mai multe dintre metodele dintr-o enumerare, daca exista, sunt simple (cum ar fi functiile accesor: get si set) si ofera solutii generice pentru simbolurile enumerarii.

Daca exista situatii care necesita mai mult decât accesul la valorile atributelor, se recomanda definirea unei noi clase si reanalizarea arhitecturii solutiei sau implementarea de solutii simple.

Pentru exemplul anterior, se adauga o noua specificatie:

  • enumerarea ofera o metoda utilizata pentru a determina daca tipul motorului polueaza sau nu;

Daca analizam tipurile de motoare putem vedea ca toate sunt poluatori cu exceptia celui electric. Deci, solutia este de a defini o metoda care va testa tipul motorului:

enum TipMotor{
    //fiecare simbol este definit prin apelul constructorului
    DIESEL(10),BENZINA(20),HYBRID(30),ELECTRIC(40);
    
    ...

    public boolean isPoluant()
    {
        if(this.toString().equals("ELECTRIC"))
            return false;
        else
            return true;
    }
// final enumerare
}

Dupa cum se observa, codul devine putin prea complex pentru o enumerare. Pentru a pastra lucrurile simple, Java ofera o alta solutie numita “constant specific class body“ (evit sa traduc acest termen, insa el poate fi interpreta ca o specializare a prelucrarilor pentru un simbol). Aceasta reprezinta o situatie in care programatorii pot defini o implementare particulara (specializare) a unei metode pentru o anumita constanta/simbol din enumerare.

Astfel, exemplul anterior se modifica prin definirea unei forme supraincarcate a metodei isPoluant() asociata simbolului ELECTRIC.

//enumerated list declared as its own class
//REMEMBER the final ; is optional
enum TipMotor{
    //fiecare simbol este creat apeland constructorul enumerarii
    DIESEL(10),
    BENZINA(20),
    HYBRID(30),
    ELECTRIC(40){
        //constant specific class body 
        //implementare particulara a unei metode
        //supradefineste implementarea generica
        public boolean isPoluant(){
            return false;
        }
    };  // ; este OBLIGATORIE cand urmeaza cod
    
    //variabila de instanta
    private int cod;
    
    //constructor
    TipMotor(int codValue){
        cod = codValue;
    }

    //metoda
    public int getCode(){
        return this.cod;
    }

    //implementarea generica a metodei
    public boolean isPoluant(){
            return true;
    }
}

In exemplul anterior, implementarea particulara a metodei isPoluant() pentru simbolul ELECTRIC are prioritate fata de implementarea generica,  furnizand o valoare specifica acestui simbol.

Elemente importante pentru examenul SCJP:

  • o enumerare este o lista de simboluri care nu sunt siruri de caractere, intregi, … doar valori constante (obiecte);
  • o variabila de tip enumerare poate avea ca valori NUMAI simboluri din enumerare;
  • atunci când se definesc enumerari, punctul si virgula din final (dupa inchiderea definitiei cu } ) este optionala;
  • enumerarile sunt clase si pot contine atribute, metode si constructori;
  • enumerarile pot fi declarat independent, la nivel global ca propria lor clasa, intr-o alta clasa, dar NU in metode;
  • NU se pot crea instante de tip enumerare prin apelul direct al constructorului;
  • se poate apela constructorul, dar numai atunci când definesc simboluri enumerarii (de exemplu, DIESEL(10));
  • exista posibilitatea de a supraincarca constructorii din enumerari si definirea lor cu orice numar de parametri;
  • metoda statica, values() , poate fi folosita pentru a itera prin lista de simboluri ale enumerarii;
  • simbolurilor enumerarii se pot asocia specializari ele unor metode (constant specific class body)utilizate pentru a supraedefini implementarea generica a metodei.

Alte subiecte care fac parte din acest tutorial Java sunt accesibile prin intermediul Java Tutorial 6 – Cuprins.