C, PHP, VB, .NET

Дневникът на Филип Петров


* Изброим тип (enum) в Java

Публикувано на 19 ноември 2015 в раздел ПИК3 Java.

Както повечето други езици, и в Java присъства изброим тип. Чрез него можем да създаваме собствени типове данни - такива с предварително предефинирани стойности. Реално зад ключовата дума "enum" стои реализиран клас, т.е. изброимите типове всъщност са обекти. Ето и нашия първи пример - създаваме изброим тип "оценка" с шест възможни стойности:

public class EnumTest{
   public static void main(String[] args){
      Ocenka x = Ocenka.SLAB;
   }
}

enum Ocenka{
 SLAB, SREDEN, DOBAR, MNOGO_DOBAR, OTLICHEN;
}

Стойностите обикновено ги пишем с главни букви, за да се екцентира върху това, че са константи, а не променливи. Това обаче не е всичко - enum типовете могат да съдържат методи. Например :

public class EnumTest{
   public static void main(String[] args){
      Ocenka x = Ocenka.SLAB;
      System.out.println(Ocenka.describe());
   }
}

enum Ocenka{
   SLAB, SREDEN, DOBAR, MNOGO_DOBAR, OTLICHEN;
   static String describe(){
      return "Оценка по шестобалната система";
   }
}

Една от странните (спрямо enum типовете от други езици) функционалности е и възможността за създаване на конструктор и предаване на параметри в него, добавянето на член-променливи, както и използването на нестатични методи. Например на всяка оценка можем да съпоставим точно определен int, който да запишем в член-променлива по следния начин:

public class EnumTest{
   public static void main(String[] args){
     Ocenka x = Ocenka.SLAB;
     System.out.println(x.getValue());
     System.out.println(x.value);
   }
}

enum Ocenka{
   SLAB(2), SREDEN(3), DOBAR(4), MNOGO_DOBAR(5), OTLICHEN(6);
 
   final int value;
   Ocenka(int value){
      this.value = value;
   }
   int getValue(){
      return this.value;
   }
 
   static String describe(){
      return "Оценка по шестобалната система";
   }
}

Причината value да е final в случая е, че обикновено не променяме числовата стойност на оценката SLAB.  Не звучи логично да имаме Слаб (8051) например, нали? Това ограничение обаче си зависи изцяло от нас - ако пожелаем, може оценката да не е final. Също така можете да си нагласите модификаторите за достъп по ваш избор - например ако искате променливата да се достъпва само чрез get метода, може да я направите private.

Конструкторът няма нужда да бъде private, защото Java няма да ви позволи сами да го извикате - той винаги се извиква само и единствено чрез една от стойностите и само и единствено чрез подадения параметър в тази стойност, който от своя страна винаги е константен и предварително дефиниран в кода на изброимия тип.

Друг начин да реализираме подобна функционалност в enum типа е като дефинираме абстрактен метод вътре в enum типа и след това го предефинираме за всяка една от неговите стойности. В случая ще дефинираме абстрактен метод print(), след което за всяка една от стойностите на изброимия тип ще я предефинираме така, че да печати в конзолата:

public class EnumTest{
   public static void main(String[] args){
     Ocenka x = Ocenka.SLAB;
     System.out.println(x.getValue());
     System.out.println(x.value);
     x.print();
   }
}

enum Ocenka{
  SLAB(2){
    void print(){
      System.out.println("Слаб 2");
    }
  }, 
  SREDEN(3){
    void print(){
      System.out.println("Среден 3");
    }
  }, 
  DOBAR(4){
    void print(){
      System.out.println("Добър 4");
    }
  }, 
  MNOGO_DOBAR(5){
  void print(){
      System.out.println("Много добър 5");
    }
  },
  OTLICHEN(6){
    void print(){
      System.out.println("Отличен 6");
    }
  };
  
  abstract void print();
  
  final int value;
  Ocenka(int value){
    this.value = value;
  }
  int getValue(){
    return this.value;
  }
  
  static String describe(){
    return "Оценка по шестобалната система";
  }
}

Така демонстрирано, може би ви става ясно как точно е реализиран клас enum - всяка от стойностите всъщност е клас наследяващ самия изброим тип и в случая този клас реализира абстрактния метод. Това също обяснява и защо е забранено сами да създаваме инстанция на изброим тип и да извикваме неговия конструктор - самия тип е абстрактен, дори да няма абстрактни методи вътре в него. Можем да инстанцираме само стойности от изброимия тип.

Алтернативно на горното решение за метод print и може би по-често използвано в практиката би било следното:

public class EnumTest{
   public static void main(String[] args){
     Ocenka x = Ocenka.SLAB;
     System.out.println(x.getValue());
     System.out.println(x.value);
     x.print();
   }
}

enum Ocenka{
  SLAB(2), SREDEN(3), DOBAR(4), MNOGO_DOBAR(5), OTLICHEN(6);
  
  final int value;
  Ocenka(int value){
    this.value = value;
  }
  int getValue(){
    return this.value;
  }
  
  void print(){
    switch(this.value){
      case 2:
        System.out.println("Слаб 2");
        break;
      case 3:
        System.out.println("Среден 3");
        break;
      case 4:
        System.out.println("Добър 4");
        break;
      case 5:
        System.out.println("Много Добър 5");
        break;
      case 6:
        System.out.println("Отличен 6");
        break;
    }
  }
  
  static String describe(){
    return "Оценка по шестобалната система";
  }
}

Изброимите типове не могат да се наследяват - те са final по дизайн. Това, което можете да направите, е да имплементирате интерфейс и да се възползвате от полиморфизъм. Например:

public class EnumTest{
   public static void main(String[] args){
     Grade g = OcenkaBG.DOBAR;
     g.print();
     g = OcenkaUS.F;
     g.print();
   }
}

interface Grade{
  void print();
  int getValue();
  static String describe(){
    return "Оценка";
  }
}

enum OcenkaBG implements Grade{
  SLAB(2), SREDEN(3), DOBAR(4), MNOGO_DOBAR(5), OTLICHEN(6);
  
  final int value;
  OcenkaBG(int value){
    this.value = value;
  }
  public int getValue(){
    return this.value;
  }
  
  public void print(){
    switch(this.value){
      case 2:
        System.out.println("Слаб 2");
        break;
      case 3:
        System.out.println("Среден 3");
        break;
      case 4:
        System.out.println("Добър 4");
        break;
      case 5:
        System.out.println("Много Добър 5");
        break;
      case 6:
        System.out.println("Отличен 6");
        break;
    }
  }
  
  static String describe(){
    return Grade.describe()+" по шестобалната система в България";
  }
}

enum OcenkaUS implements Grade{
  A(4){
    public void print(){
      System.out.println("Excellent A");
    }
  }, 
  B(3){
    public void print(){
      System.out.println("Very Good B");
    }
  }, 
  C(2){
    public void print(){
      System.out.println("Good C");
    }
  }, 
  D(1){
    public void print(){
      System.out.println("Average D");
    }
  }, 
  F(0){
    public void print(){
      System.out.println("Poor F");
    }
  };
  
  public abstract void print();
    
  final int value;
  OcenkaUS(int value){
    this.value = value;
  }
  public int getValue(){
    return this.value;
  }
  
  static String describe(){
    return Grade.describe()+" по шестобалната система в САЩ";
  }
}

В горния пример нарочно реализирахме двата вида оценки по двата различни начина. Нека сега демонстрираме още една техника - превръщане на една оценка в друга. За целта ще си създадем статичен "mapping" за това коя оценка от едната система на коя оценка от другата отговаря.

public class EnumTest{
   public static void main(String[] args){
     Grade g = OcenkaBG.DOBAR;
     g.print();
     g = ((OcenkaBG)g).convertToUS();
     g.print();
     g = ((OcenkaUS)g).convertToBG();
     g.print();
   }
}

interface Grade{
  void print();
  int getValue();
  static String describe(){
    return "Оценка";
  }
}

enum OcenkaBG implements Grade{
  SLAB(2), SREDEN(3), DOBAR(4), MNOGO_DOBAR(5), OTLICHEN(6);
 
  private OcenkaUS usformat;
  static{
    SLAB.usformat = OcenkaUS.F;
    SREDEN.usformat = OcenkaUS.D;
    DOBAR.usformat = OcenkaUS.C;
    MNOGO_DOBAR.usformat = OcenkaUS.B;
    OTLICHEN.usformat = OcenkaUS.A;
  }
  public OcenkaUS convertToUS(){
    return this.usformat;
  }
  
  final int value;
  OcenkaBG(int value){
    this.value = value;
  }
  public int getValue(){
    return this.value;
  }
  
  public void print(){
    switch(this.value){
      case 2:
        System.out.println("Слаб 2");
        break;
      case 3:
        System.out.println("Среден 3");
        break;
      case 4:
        System.out.println("Добър 4");
        break;
      case 5:
        System.out.println("Много Добър 5");
        break;
      case 6:
        System.out.println("Отличен 6");
        break;
    }
  }
  
  static String describe(){
    return Grade.describe()+" по шестобалната система в България";
  }
}

enum OcenkaUS implements Grade{
  A(4){
    { super.bgformat = OcenkaBG.OTLICHEN; }
    public void print(){
      System.out.println("Excellent A");
    }
  }, 
  B(3){
    { super.bgformat = OcenkaBG.MNOGO_DOBAR; }
    public void print(){
      System.out.println("Very Good B");
    }
  }, 
  C(2){
    public void print(){
      { super.bgformat = OcenkaBG.DOBAR; }
      System.out.println("Good C");
    }
  }, 
  D(1){
    { super.bgformat = OcenkaBG.SREDEN; }
    public void print(){
      System.out.println("Average D");
    }
  }, 
  F(0){
    public void print(){
      { super.bgformat = OcenkaBG.SLAB; }
      System.out.println("Poor F");
    }
  };
  
  private OcenkaBG bgformat;
  public OcenkaBG convertToBG(){
    return this.bgformat;
  }
  
  public abstract void print();
    
  final int value;
  OcenkaUS(int value){
    this.value = value;
  }
  public int getValue(){
    return this.value;
  }
  
  static String describe(){
    return Grade.describe()+" по шестобалната система в САЩ";
  }
}

Отново показахме реализация на два различни начина за постигането на една и съща цел. В OcenkaBG реализирахме въпросния "mapping" чрез статично инициализиране "static{...}", а в OcenkaUS го реализирахме нестатично. Интересна от техническа гледна точка подробност е, че нестатичния инициализатор на стойностите на enum типа има достъп до private променлива на своя супер тип (самия enum) - това го правите когато извиквате super.bgformat в горния код. Това е строго специфично за enum типовете и не е възможно при обикновените класове. Например следното няма да може да се компилира:

public class Test{
   private int x;
}

class Test2 extends Test{
  { super.x = 5; }
}

Грешката ще бъде, че нямате достъп до private променливата на супер класа.

Досега не бяхме говорили за статични и нестатични инициализатори. На този етап можете да ги приемете като методи за инициализиране на променливи, които са общи за всички конструктори. Тези инициализационни блокове се извикват преди извикването на конструктора на класа.

Накрая ще покажем и други неща, които можете да правите с enum:

 OcenkaBG g = OcenkaBG.DOBAR;
 // Ще отпечати "DOBAR"
 System.out.println(g.toString());
 
 // Ще отпечати индекса на типа - обикновено не нитрябва
 System.out.println(g.ordinal());

 // Ще отпечати "OcenkaBG"
 System.out.println(g.getDeclaringClass().getName());

 OcenkaBG g2 = OcenkaBG.SREDEN;
 // Важно - сравнението е по индекси - резултата от ordinal()
 // Затова при сравнение е важно в какъв ред са декларирани стойностите
 // Добре е да спазвате някаква конвенция - например малки към големи
 // В горните примери OcenkaBG е от малки към големи, а OcenkaUS наобратно
 // Това можете сами да се досетите, че е МНОГО ЛОШО ако използваме compareTo
 if(g.compareTo(g2)>0){
    System.out.println(g.toString()+" > "+ g2.toString());
 }
 else{
    System.out.println(g.toString()+" <= "+ g2.toString());
 }

 // Отпечатваме информация за всяка една стойност
 for(OcenkaBG o: OcenkaBG.values()){
   System.out.println(o.toString()+": ");
   o.print();
 }

 



Добави коментар

Адресът на електронната поща няма да се публикува


*