2013-03-31 16 views
47

Próbowałem zrozumieć, w jaki sposób enum Java działa naprawdę i doszedłem do wniosku, że jest bardzo podobny do normalnej klasy Java, której konstruktor został zadeklarowany jako prywatny.Jakie są różnice między wyliczeniem Java a klasą z prywatnym konstruktorem?

Właśnie doszedłem do tego wniosku i nie opiera się to na zbytnim myśleniu, ale chciałbym się dowiedzieć, czy coś mi brakuje.

Poniżej znajduje się implementacja prostego wyliczenia Java i równoważnej klasy Java.

public enum Direction { 
    ENUM_UP(0, -1), 
    ENUM_DOWN(0, 1), 
    ENUM_RIGHT(1, 0), 
    ENUM_LEFT(-1, 0); 


    private int x; 
    private int y; 

    private Direction(int x, int y){ 
     this.x = x; 
     this.y = y; 
    } 
    public int getEnumX(){ 
     return x; 
    } 
    public int getEnumY(){ 
     return y; 
    } 
} 

Jaka jest różnica w rozumieniu kodu powyżej i poniżej?

public class Direction{ 
    public static final Direction UP = new Direction(0, -1) ; 
    public static final Direction DOWN = new Direction(0, 1) ; 
    public static final Direction LEFT = new Direction(-1, 0) ; 
    public static final Direction RIGHT = new Direction(1, 0) ; 


    private int x ; 
    private int y ; 

    private Direction(int x, int y){ 
     this.x = x ; 
     this.y = y ; 
    } 
    public int getX(){ 
     return x; 
    } 
    public int getY(){ 
     return y; 
    } 
} 
+7

Dobre pytanie, są one pod wieloma względami podobne :) Z dużą ilością różnic jestem pewien, że dobre odpowiedzi tutaj zarysują. –

+2

Co najmniej trzy różnice, które mogę wymyślić: [1] nie będziesz w stanie zmienić drugiej implementacji (tj. 'Kierunek d, przełącznik (d)'), [2] 'Direction.toString()' implementacje będą różne, [3] Metoda enum pozwala uzyskać wszystkie wystąpienia "klasy" za pomocą '.values ​​()', podczas gdy metoda klasy nie. – jedwards

+2

Utracisz wartości 'values ​​()', 'valueOf()' oraz 'ordinal()'. –

Odpowiedz

46

Różnice:

  1. wyliczenia rozciągają java.lang.Enum i zdobyć wszystkie jego nice features:
    1. Automatyczne Singleton zachowanie przez
    2. Automatyczne czytelny dla człowieka .toString metoda na wartości enum bez konieczności powtórzenia prawidłowego serializacji swoje nazwy enum
    3. .name i .ordinal metody specjalnego przeznaczenia
    4. użytkowa w wysokiej wydajności bitset oparte EnumSet i EnumMap klas
  2. wyliczenia są traktowane przez język szczególnie:
    1. wyliczenia użyć specjalnej składni, które upraszcza tworzenie instancji bez pisania kilkudziesięciu public static final pól
    2. Wyliczeń można używać w instrukcjach
    3. Wyliczeń nie można tworzyć poza listą wyliczeń, chyba że zastosowano odbicie
    4. wyliczenia nie może zostać przedłużony poza listy wyliczenia
  3. Java automatycznie kompiluje dodatkowych rzeczy w teksty stałe:
    1. public static (Enum)[] values();
    2. public static (Enum) valueOf(java.lang.String);
    3. private static final (Enum)[] $VALUES; (values() powraca klonem tego)

Większość z nich można emulować z odpowiednio zaprojektowaną klasą, ale Enum po prostu sprawia, że ​​bardzo proste jest stworzenie klasy z tym zestawem szczególnie pożądanych właściwości.

+0

jak działa singleton? dla każdej wartości ma singleton? co jeśli stworzysz wyliczenie, które ma ustawione i pobierające? czy użycie setterów na jednej zmiennej wyliczeniowej wpłynęłoby na inne zmienne o tej samej wartości? –

+0

Każda wartość wyliczenia jest pojedynczą, więc tak, użycie setera na jednym wpłynie na wszystkie zmienne odnoszące się do tej wartości. Dlatego zdecydowanie zaleca się, aby nigdy nie mieć zmiennego stanu w swoich elementach. – nneonneo

+0

Rozumiem. wydaje się logiczne. dzięki. –

6

Spójrz na this blogpage, opisuje jak Java enum s są kompilowane do kodu bajtowego. Zobaczysz, że jest mały dodatek w porównaniu z drugim przykładem kodu, który jest tablicą obiektów o nazwie Direction o nazwie VALUES. Tablica ta posiada wszystkie możliwe wartości dla wyliczenia, więc nie będzie w stanie zrobić

new Direction(2, 2) 

(na przykład przy użyciu odbicia), a następnie użyć jej jako ważnej wartości Direction.

Plus, jak @ Eng.Fouad poprawnie wyjaśnia, nie masz values(), valueOf() i ordinal().

+1

Wiem, że możesz * hakować * prywatnego konstruktora klasy 'Direction' za pomocą refleksji i tworzyć nowe instancje, ale nie jestem pewien, czy możesz zrobić to samo z wyliczaniem. –

+1

Zgodnie ze sposobem, w jaki są skompilowane do kodu bajtowego, możesz być w stanie. Nigdy nie próbowałem siebie, ciekawy eksperyment ... – mthmulders

0

Główna różnica polega na tym, że każda klasa enum niejawnie rozszerza klasę Enum<E extends Enum<E>>. Powoduje to, że:

  1. enum przedmioty mają takie metody jak name() i ordinal()
  2. enum przedmioty mają szczególne toString(), hashCode(), equals() i compareTo() implementacje
  3. enum przedmioty odpowiednie do switch operatora.

Wszystkie wyżej wymienione informacje nie dotyczą Twojej wersji klasy Direction. To jest "znaczenie" różnica.

6

Aby odpowiedzieć na pytanie: w zasadzie nie ma różnicy między tymi dwoma podejściami. Jednak enum construct oferuje dodatkowe metody wspomagające, takie jak: values(), valueOf() itd., Które trzeba napisać samemu, stosując metodę klasy z prywatnym konstruktorem.

Ale tak, podoba mi się to, że Java wylicza się w większości tak, jak inne klasy w Javie, mogą mieć pola, zachowania, itp. Ale dla mnie to, co oddziela wyliczenia od zwykłych klas, to idea, że ​​wyliczenia są klasami/typami których wystąpienia/członkowie są z góry określone. W przeciwieństwie do zwykłych klas, w których można tworzyć dowolną liczbę wystąpień, wyliczenia ograniczają tylko tworzenie do znanych instancji. Tak, jak to zilustrowałeś, możesz to zrobić również w przypadku klas z prywatnymi konstruktorami, ale wyliczenia powodują, że jest to bardziej intuicyjne.

4

Jak ludzie zauważyli, tracisz values(), valueOf() i ordinal().Możesz replikować to zachowanie dość łatwo, używając kombinacji Map i List.

public class Direction { 

    public static final Direction UP = build("UP", 0, -1); 
    public static final Direction DOWN = build("DOWN", 0, 1); 
    public static final Direction LEFT = build("LEFT", -1, 0); 
    public static final Direction RIGHT = build("RIGHT", 1, 0); 
    private static final Map<String, Direction> VALUES_MAP = new LinkedHashMap<>(); 
    private static final List<Direction> VALUES_LIST = new ArrayList<>(); 
    private final int x; 
    private final int y; 
    private final String name; 

    public Direction(int x, int y, String name) { 
     this.x = x; 
     this.y = y; 
     this.name = name; 
    } 

    private static Direction build(final String name, final int x, final int y) { 
     final Direction direction = new Direction(x, y, name); 
     VALUES_MAP.put(name, direction); 
     VALUES_LIST.add(direction); 
     return direction; 
    } 

    public int getX() { 
     return x; 
    } 

    public int getY() { 
     return y; 
    } 

    public static Direction[] values() { 
     return VALUES_LIST.toArray(new Direction[VALUES_LIST.size()]); 
    } 

    public static Direction valueOf(final String direction) { 
     if (direction == null) { 
      throw new NullPointerException(); 
     } 
     final Direction dir = VALUES_MAP.get(direction); 
     if (dir == null) { 
      throw new IllegalArgumentException(); 
     } 
     return dir; 
    } 

    public int ordinal() { 
     return VALUES_LIST.indexOf(this); 
    } 

    @Override 
    public int hashCode() { 
     int hash = 7; 
     hash = 29 * hash + name.hashCode(); 
     return hash; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     final Direction other = (Direction) obj; 
     return name.equals(other.name); 
    } 

    @Override 
    public String toString() { 
     return name; 
    } 
} 

Jak widać; kod bardzo szybko się kręci.

Nie jestem pewien, czy istnieje sposób na replikację oświadczenia switch z tą klasą; więc stracisz to.

Powiązane problemy