Czy można dodawać podziały w celu dodania nowych elementów?


534

Chcę wziąć istniejący wyliczenie i dodać do niego więcej elementów w następujący sposób:

enum A {a,b,c}

enum B extends A {d}

/*B is {a,b,c,d}*/

Czy jest to możliwe w Javie?


12
Powodem tego jest przetestowanie sytuacji, w której istnieje niepoprawna wartość wyliczenia bez wprowadzenia niepoprawnej wartości wyliczenia w źródle rdzenia.
Archimedes Trajano

Tak, przykład „językowej” czystości. Myślę, że to, co jest pożądane, to pomysł „księgowości” na oszczędzanie siły roboczej zestawu liczb całkowitych z automatyczną inkrementacją, taki jak w C ++, aby można było rozpocząć nowy zestaw jako rozszerzenie starego zestawu, zaczynając od 1+ ostatniej wartości wcześniejszy zestaw, a jeśli wpisy są nazwane, dziedziczą nazwy z „wspólnego podzbioru”. Chociaż wyliczanie języka Java ma w tym kilka fajnych rzeczy, brakuje mu prostej automatycznej automatycznej inkrementacji liczb całkowitych, która zapewnia pomoc wyliczania w C ++.
peterk

4
W rzeczywistości, kiedy rozszerzasz swój enum o nowe wartości, tworzysz nie podklasę, ale nadklasę. Możesz używać bazowych wartości wyliczeniowych wszędzie zamiast „rozszerzonego” wyliczenia, ale nie odwrotnie, więc zgodnie z zasadą podstawienia Liskowa, rozszerzony wyliczanie jest nadklasą wyliczenia podstawowego.
Ilya

@Ilya ... tak, to prawda. Zwracam uwagę, że pytanie ma konkretne przypadki użycia w świecie rzeczywistym. Czysto teoretycznie, za podstawę Enum z: PrimaryColours; zasadne jest chcą Super -class to Enum PrimaryAndPastelColoursprzez dodanie nowych nazw kolorów. Liskov wciąż jest słoniem w pokoju. Więc dlaczego nie zacząć od bazy Enum z: AllMyColours- a potem jeden może sub -class wszystkich kolorach do: PrimaryAndPastelColoursa następnie sub -class to: PrimaryColours(zachowując hierarchię w umyśle). Java też na to nie pozwala.
będzie

Odpowiedzi:


450

Nie, nie możesz tego zrobić w Javie. Oprócz czegokolwiek innego, dprawdopodobnie byłby to przypadek A(biorąc pod uwagę normalną ideę „rozszerzenia”), ale użytkownicy, którzy tylko wiedzieli o Atym, nie wiedzieliby o tym - co pokonuje sens wyliczenia będącego dobrze znanym zestawem wartości.

Jeśli możesz nam powiedzieć więcej o tym, jak chcesz z tego korzystać , możemy zaproponować alternatywne rozwiązania.


516
Wszystkie wyliczenia domyślnie rozszerzają java.lang.Enum. Ponieważ Java nie obsługuje wielokrotnego dziedziczenia, wyliczanie nie może rozszerzać niczego innego.
ofiarować

9
Powodem, dla którego chcę rozszerzyć, jest to, że chciałbym mieć klasę podstawową o nazwie np. IntEnum, która wygląda następująco: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Wówczas wszystkie moje wyliczenia mogłyby go rozszerzyć ... w tym przypadku po prostu korzystałem z dziedziczenia, a zatem nie musiałbym często kopiować tego kodu „wyliczania int”. Jestem nowy w Javie i pochodzę z C # i mam nadzieję, że coś mi umknęło. Moje obecne zdanie jest takie, że wyliczenia Java są uciążliwe w porównaniu do C #.
Tyler Collier

30
@Tyler: C # enum to tylko nazwy związane z liczbami, bez automatycznej weryfikacji lub czegokolwiek . Wyliczenia IMO to jeden kawałek Java, który jest rzeczywiście lepszy niż C #.
Jon Skeet

21
Nie zgadzam się z @JonSkeet tutaj. W moim przypadku chciałbym oddzielić całą nieprzyjemną logikę w moim wielkim wyliczeniu, ukryć logikę i zdefiniować czyste wyliczenie, które rozszerza drugą, która jest ukryta. Wyliczenia z dużą ilością logiki przewyższają pomysł zadeklarowania czystych zmiennych, więc nie trzeba deklarować setek zmiennych ciągów statycznych, więc klasa z 5 wyliczeniami nie staje się nieczytelna i zbyt duża w liniach. Nie chcę też, aby inni programiści zajmowali się kopiowaniem i wklejaniem tego spokoju kodu dla następnego projektu i zamiast tego rozszerzali podstawową wartość ... to ma dla mnie sens ...
mmm

43
@givanse ... nie zgadzam się z tobą w kwestii domniemanego rozszerzenia java.lang.Enum jest przyczyną niedziedziczenia, ponieważ każda klasa w java również domyślnie dziedziczy klasę Object, ale może odziedziczyć inną klasę, tak jak wtedy do hierarchii, jak Object->A->BzamiastObject->A->B extends Object
mickeymoon

317

Wyliczenia stanowią pełne wyliczenie możliwych wartości. Tak więc (niepomocna) odpowiedź brzmi „nie”.

Przykładem prawdziwego problemu są dni powszednie, dni weekendowe i związek zawodowy, dni tygodnia. Możemy zdefiniować wszystkie dni w ciągu tygodnia, ale wtedy nie będziemy w stanie reprezentować właściwości specjalnych dla dni powszednich i weekendowych.

Co możemy zrobić, to mieć trzy typy wyliczeń z mapowaniem między dniami powszednimi / weekendowymi i dniami tygodnia.

public enum Weekday {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

Alternatywnie, możemy mieć otwarty interfejs na dzień tygodnia:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
}
public enum WeekendDay implements Day {
    SAT, SUN;
}

Lub możemy połączyć dwa podejścia:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay implements Day {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
    public Day toDay() { ... }
}

20
Czy nie ma z tym problemu? Instrukcja switch nie będzie działać na interfejsie, ale działa na zwykłym wyliczaniu. Niedziałanie z przełącznikiem zabija jedną z ładniejszych rzeczy w wyliczeniach.
Manius

9
Myślę, że może być z tym kolejny problem. Nie ma równości między Weekday.MON i DayOfWeek.MON. Czy to nie jest kolejna wielka zaleta enum? Nie mam lepszego rozwiązania, po prostu zdaję sobie z tego sprawę, gdy próbuję znaleźć najlepszą odpowiedź. Brak możliwości użycia == zmusza nieco rękę.
Snekse

2
@Crusader tak, to jest właśnie kompromis. Jeśli chcesz czegoś rozwijanego, nie możesz mieć stałych instrukcji przełączania, jeśli chcesz zestawu znanych znanych wartości, tautologicznie nie możesz mieć czegoś rozwijanego.
djechlin

3
Przechodząc od wyliczenia do interfejsu, tracisz również statyczne wywołanie wartości (). Utrudnia to refaktoryzację, szczególnie jeśli zdecydujesz się rozszerzyć swój wyliczenie i dodać interfejs jako barierę abstrakcji do ustalonego wyliczenia.
Joshua Goldberg

4
Takie podejście do wyliczania wyliczenia z interfejsu jest używane przez API Java 1.7, np. Java.nio.file.Files.write () przyjmuje tablicę OpenOption jako ostatni argument. OpenOption jest interfejsem, ale kiedy wywołujemy tę funkcję, zwykle przekazujemy stałą wyliczania StandardOpenOption, która jest pochodną OpenOption. Ma to tę zaletę, że jest rozszerzalne, ale ma również wady. Implementacja cierpi z powodu faktu, że OpenOption jest interfejsem. Tworzy zestaw HashSet <OpenOption> na podstawie przekazanej tablicy, kiedy mógł stworzyć bardziej energooszczędny i zajmujący dużo czasu EnumSet. I nie można użyć przełącznika.
Klitos Kyriacou,

71

Zalecanym rozwiązaniem tego problemu jest rozszerzalny wzór wyliczenia .

Obejmuje to utworzenie interfejsu i korzystanie z tego, w którym obecnie używasz wyliczenia. Następnie spraw, aby enum zaimplementował interfejs. Możesz dodać więcej stałych, sprawiając, że nowe wyliczenie rozszerzy również interfejs.


Warto przywołać ich użycie fabrycznej metody w interfejsie. Świetny sposób na dzielenie wspólnej funkcjonalności między powiązanymi Enums, biorąc pod uwagę, że rozszerzenie nie jest realnym rozwiązaniem.
Tim Clemons

8
Czy możesz podać więcej szczegółów (kod :)) na temat tego wzoru?
Dherik,

3
Ten wzorzec nie pozwala na rozszerzenie wartości wyliczenia. O to właśnie chodzi w pytaniu.
Eria,

55

Pod pokrywami twój ENUM jest zwykłą klasą generowaną przez kompilator. Ta wygenerowana klasa rozszerza się java.lang.Enum. Technicznym powodem, dla którego nie można rozszerzyć wygenerowanej klasy, jest to, że wygenerowana klasa jest final. W tym temacie omówiono koncepcyjne przyczyny ostateczności. Ale dodam mechanikę do dyskusji.

Oto wyliczenie testowe:

public enum TEST {  
    ONE, TWO, THREE;
}

Kod wynikowy z javap:

public final class TEST extends java.lang.Enum<TEST> {
  public static final TEST ONE;
  public static final TEST TWO;
  public static final TEST THREE;
  static {};
  public static TEST[] values();
  public static TEST valueOf(java.lang.String);
}

Możliwe, że możesz wpisać tę klasę samodzielnie i upuścić „końcowy”. Ale kompilator uniemożliwia bezpośrednie rozszerzenie pliku „java.lang.Enum”. Możesz zdecydować, aby NIE rozszerzyć java.lang.Enum, ale wtedy twoja klasa i klasy pochodne nie byłyby instancją java.lang.Enum ... co może tak naprawdę nie mieć dla ciebie znaczenia!


1
Co robi pusty blok statyczny? „static {};”
ukończył

1
Nie ma w nim kodu. Program „javap” pokazuje pusty blok.
ChrisCantrell,

Dziwnie go mieć, jeśli nic nie robi, prawda?
ukończył

4
Masz rację! Mój błąd. NIE jest to pusty blok kodu. Jeśli uruchomisz „javap -c”, zobaczysz rzeczywisty kod w bloku statycznym. Blok statyczny tworzy wszystkie instancje ENUM (tutaj JEDEN, DWA i TRZY). Przepraszam za to.
ChrisCantrell,

1
Dziękuję za stwierdzenie prostego faktu: ponieważ java.lang.Enum zostaje uznany za ostateczny.
Benjamin

26
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

można zapisać jako:

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • ClassGroup.B.getMembers () zawiera {a, b, c, d}

Jak może być przydatny: Powiedzmy, że chcemy czegoś takiego: Mamy wydarzenia i używamy wyliczeń. Te wyliczenia można pogrupować przez podobne przetwarzanie. Jeśli mamy operację z wieloma elementami, niektóre zdarzenia rozpoczynają działanie, niektóre są tylko krokami, a inne kończą działanie. Aby zebrać taką operację i uniknąć długiej skrzynki przełączników, możemy zgrupować je jak w przykładzie i użyć:

if(myEvent.is(State_StatusGroup.START)) makeNewOperationObject()..
if(myEnum.is(State_StatusGroup.STEP)) makeSomeSeriousChanges()..
if(myEnum.is(State_StatusGroup.FINISH)) closeTransactionOrSomething()..

Przykład:

public enum AtmOperationStatus {
STARTED_BY_SERVER       (State_StatusGroup.START),
SUCCESS             (State_StatusGroup.FINISH),
FAIL_TOKEN_TIMEOUT      (State_StatusGroup.FAIL, 
                    State_StatusGroup.FINISH),
FAIL_NOT_COMPLETE       (State_StatusGroup.FAIL,
                    State_StatusGroup.STEP),
FAIL_UNKNOWN            (State_StatusGroup.FAIL,
                    State_StatusGroup.FINISH),
(...)

private AtmOperationStatus(StatusGroupInterface ... pList){
    for (StatusGroupInterface group : pList){
        group.addMember(this);
    }
}
public boolean is(StatusGroupInterface with){
    for (AtmOperationStatus eT : with.getMembers()){
        if( eT .equals(this))   return true;
    }
    return false;
}
// Each group must implement this interface
private interface StatusGroupInterface{
    EnumSet<AtmOperationStatus> getMembers();
    void addMember(AtmOperationStatus pE);
}
// DEFINING GROUPS
public enum State_StatusGroup implements StatusGroupInterface{
    START, STEP, FAIL, FINISH;

    private List<AtmOperationStatus> members = new LinkedList<AtmOperationStatus>();

    @Override
    public EnumSet<AtmOperationStatus> getMembers() {
        return EnumSet.copyOf(members);
    }

    @Override
    public void addMember(AtmOperationStatus pE) {
        members.add(pE);
    }
    static { // forcing initiation of dependent enum
        try {
            Class.forName(AtmOperationStatus.class.getName()); 
        } catch (ClassNotFoundException ex) { 
            throw new RuntimeException("Class AtmEventType not found", ex); 
        }
    }
}
}
//Some use of upper code:
if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.FINISH)) {
    //do something
}else if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.START)) {
    //do something      
}  

Dodaj bardziej zaawansowane:

public enum AtmEventType {

USER_DEPOSIT        (Status_EventsGroup.WITH_STATUS,
              Authorization_EventsGroup.USER_AUTHORIZED,
              ChangedMoneyAccountState_EventsGroup.CHANGED,
              OperationType_EventsGroup.DEPOSIT,
              ApplyTo_EventsGroup.CHANNEL),
SERVICE_DEPOSIT     (Status_EventsGroup.WITH_STATUS,
              Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
              ChangedMoneyAccountState_EventsGroup.CHANGED,
              OperationType_EventsGroup.DEPOSIT,
              ApplyTo_EventsGroup.CHANNEL),
DEVICE_MALFUNCTION  (Status_EventsGroup.WITHOUT_STATUS,
              Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
              ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED,
              ApplyTo_EventsGroup.DEVICE),
CONFIGURATION_4_C_CHANGED(Status_EventsGroup.WITHOUT_STATUS,
              ApplyTo_EventsGroup.TERMINAL,
              ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED),
(...)

Powyżej, jeśli mamy pewne błędy (myEvent.is (State_StatusGroup.FAIL)), to iterując poprzednie zdarzenia, możemy łatwo sprawdzić, czy musimy cofnąć przelew pieniędzy poprzez:

if(myEvent2.is(ChangedMoneyAccountState_EventsGroup.CHANGED)) rollBack()..

Może być przydatny do:

  1. w tym wyraźne metadane dotyczące logiki przetwarzania, mniej do zapamiętania
  2. wdrażanie części dziedziczenia
  3. nie chcemy używać struktur klas, np. do wysyłania krótkich komunikatów o stanie

13

Oto sposób, w jaki znalazłem, jak rozszerzyć wyliczenie na inny wyliczenie, jest bardzo proste podejście:

Zakładając, że masz wyliczenie ze wspólnymi stałymi:

public interface ICommonInterface {

    String getName();

}


public enum CommonEnum implements ICommonInterface {
    P_EDITABLE("editable"),
    P_ACTIVE("active"),
    P_ID("id");

    private final String name;

    EnumCriteriaComun(String name) {
        name= name;
    }

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

możesz spróbować wykonać instrukcję rozszerzającą w następujący sposób:

public enum SubEnum implements ICommonInterface {
    P_EDITABLE(CommonEnum.P_EDITABLE ),
    P_ACTIVE(CommonEnum.P_ACTIVE),
    P_ID(CommonEnum.P_ID),
    P_NEW_CONSTANT("new_constant");

    private final String name;

    EnumCriteriaComun(CommonEnum commonEnum) {
        name= commonEnum.name;
    }

    EnumCriteriaComun(String name) {
        name= name;
    }

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

oczywiście za każdym razem, gdy musisz rozszerzyć stałą, musisz zmodyfikować swoje pliki SubEnum.


ciekawe, moglibyśmy również użyć bardzo enum toString (), a na końcu porównać ciągi; i aby użyć przełącznika, musielibyśmy po prostu rzucić obiekt na znany wyliczenie; jedynym problemem byłyby 2 programistów rozszerzających i tworzących identyczny identyfikator enum, a później próbujących scalić oba kody :), teraz myślę, że rozumiem, dlaczego enum powinno pozostać nierozszerzalne.
Aquarius Power

11

Na wypadek, gdybyś go przeoczył, jest rozdział w doskonałej książce Joshua Blocha „ Java Effective, 2nd edition ”.

  • Rozdział 6 - Wyliczenia i adnotacje
    • Punkt 34: Emuluj rozszerzalne wyliczenia za pomocą interfejsów

Wyodrębnij tutaj .

Właśnie wniosek:

Niewielką wadą zastosowania interfejsów do emulacji rozszerzalnych wyliczeń jest to, że implementacje nie mogą być dziedziczone z jednego rodzaju wyliczenia na inny. W przypadku naszego przykładu Operacja logika do przechowywania i pobierania symbolu związanego z operacją jest duplikowana w BasicOperation i ExtendedOperation. W tym przypadku nie ma to znaczenia, ponieważ bardzo mało kodu jest duplikowane. Gdyby była większa ilość współużytkowanej funkcjonalności, można by ją zamknąć w klasie pomocniczej lub statycznej metodzie pomocniczej, aby wyeliminować duplikację kodu.

Podsumowując, chociaż nie można napisać rozszerzalnego typu wyliczenia, można go emulować, pisząc interfejs zgodny z podstawowym typem wyliczenia, który implementuje interfejs. Pozwala to klientom pisać własne wyliczenia, które implementują interfejs. Tych wyliczeń można następnie używać wszędzie tam, gdzie można zastosować podstawowy typ wyliczania, zakładając, że interfejsy API są napisane pod względem interfejsu.


6

Staram się unikać wyliczeń, ponieważ nie są one rozszerzalne. Aby pozostać na przykładzie OP, jeśli A znajduje się w bibliotece, a B we własnym kodzie, nie można rozszerzyć A, jeśli jest to wyliczenie. Tak czasami zastępuję wyliczenia:

// access like enum: A.a
public class A {
    public static final A a = new A();
    public static final A b = new A();
    public static final A c = new A();
/*
 * In case you need to identify your constant
 * in different JVMs, you need an id. This is the case if
 * your object is transfered between
 * different JVM instances (eg. save/load, or network).
 * Also, switch statements don't work with
 * Objects, but work with int.
 */
    public static int maxId=0;
    public int id = maxId++;
    public int getId() { return id; }
}

public class B extends A {
/*
 * good: you can do like
 * A x = getYourEnumFromSomeWhere();
 * if(x instanceof B) ...;
 * to identify which enum x
 * is of.
 */
    public static final A d = new A();
}

public class C extends A {
/* Good: e.getId() != d.getId()
 * Bad: in different JVMs, C and B
 * might be initialized in different order,
 * resulting in different IDs.
 * Workaround: use a fixed int, or hash code.
 */
    public static final A e = new A();
    public int getId() { return -32489132; };
}

Jest kilka dołów, których należy unikać, zobacz komentarze w kodzie. W zależności od potrzeb jest to solidna, rozszerzalna alternatywa dla enum.


1
może być w porządku, jeśli potrzebujesz tylko porządków na instancje. Ale wyliczenia mają również właściwość nazwy, która jest dość przydatna.
Inor

6

W ten sposób poprawiam wzorzec dziedziczenia wyliczania za pomocą sprawdzania czasu wykonywania w inicjalizatorze statycznym. Do BaseKind#checkEnumExtenderkontroli, że „rozszerzenie” enum deklaruje wartości wyliczenia podstawy w dokładnie taki sam sposób, #name()i #ordinal()pozostają w pełni kompatybilne.

Nadal istnieje deklaracja kopiuj-wklej dla deklarowania wartości, ale program szybko się nie powiedzie, jeśli ktoś doda lub zmodyfikuje wartość w klasie podstawowej bez aktualizacji rozszerzających.

Typowe zachowanie różnych wyliczeń, które się wzajemnie rozszerzają:

public interface Kind {
  /**
   * Let's say we want some additional member.
   */
  String description() ;

  /**
   * Standard {@code Enum} method.
   */
  String name() ;

  /**
   * Standard {@code Enum} method.
   */
  int ordinal() ;
}

Podstawowy wyliczenie, z metodą weryfikacyjną:

public enum BaseKind implements Kind {

  FIRST( "First" ),
  SECOND( "Second" ),

  ;

  private final String description ;

  public String description() {
    return description ;
  }

  private BaseKind( final String description ) {
    this.description = description ;
  }

  public static void checkEnumExtender(
      final Kind[] baseValues,
      final Kind[] extendingValues
  ) {
    if( extendingValues.length < baseValues.length ) {
      throw new IncorrectExtensionError( "Only " + extendingValues.length + " values against "
          + baseValues.length + " base values" ) ;
    }
    for( int i = 0 ; i < baseValues.length ; i ++ ) {
      final Kind baseValue = baseValues[ i ] ;
      final Kind extendingValue = extendingValues[ i ] ;
      if( baseValue.ordinal() != extendingValue.ordinal() ) {
        throw new IncorrectExtensionError( "Base ordinal " + baseValue.ordinal()
            + " doesn't match with " + extendingValue.ordinal() ) ;
      }
      if( ! baseValue.name().equals( extendingValue.name() ) ) {
        throw new IncorrectExtensionError( "Base name[ " + i + "] " + baseValue.name()
            + " doesn't match with " + extendingValue.name() ) ;
      }
      if( ! baseValue.description().equals( extendingValue.description() ) ) {
        throw new IncorrectExtensionError( "Description[ " + i + "] " + baseValue.description()
            + " doesn't match with " + extendingValue.description() ) ;
      }
    }
  }


  public static class IncorrectExtensionError extends Error {
    public IncorrectExtensionError( final String s ) {
      super( s ) ;
    }
  }

}

Próbka rozszerzenia:

public enum ExtendingKind implements Kind {
  FIRST( BaseKind.FIRST ),
  SECOND( BaseKind.SECOND ),
  THIRD( "Third" ),
  ;

  private final String description ;

  public String description() {
    return description ;
  }

  ExtendingKind( final BaseKind baseKind ) {
    this.description = baseKind.description() ;
  }

  ExtendingKind( final String description ) {
    this.description = description ;
  }

}

4

W oparciu o @Tom Hawtin - szybką odpowiedź dodajemy obsługę przełączników,

interface Day<T> {
    ...
  T valueOf();
}

public enum Weekday implements Day<Weekday> {
    MON, TUE, WED, THU, FRI;
   Weekday valueOf(){
     return valueOf(name());
   }
}

public enum WeekendDay implements Day<WeekendDay> {
    SAT, SUN;
   WeekendDay valueOf(){
     return valueOf(name());
   }
}

Day<Weekday> wds = Weekday.MON;
Day<WeekendDay> wends = WeekendDay.SUN;

switch(wds.valueOf()){
    case MON:
    case TUE:
    case WED:
    case THU:
    case FRI:
}

switch(wends.valueOf()){
    case SAT:
    case SUN:
}

Jaki jest pożytek z tej valueOf()metody?
Axel Advento

@AxelAdvento Chodzi o to, że polegamy na interfejsie, Dayktóry ma metodę, valueOf()a następnie switch(Day.valueOf())jest implementowany przez WeekDay, WeekEndDaywyliczenia.
Khaled Lela,

3

Sugeruję, abyś odwrócił podejście.

Zamiast rozszerzać istniejące wyliczenie, utwórz większe i utwórz jego podzbiór. Na przykład, jeśli miałeś wyliczenie o nazwie PET i chciałeś rozszerzyć go na ZWIERZĘTA, powinieneś to zrobić:

public enum ANIMAL {
    WOLF,CAT, DOG
} 
EnumSet<ANIMAL> pets = EnumSet.of(ANIMAL.CAT, ANIMAL.DOG);

Uważaj, zwierzęta nie są niezmiennymi kolekcjami, możesz chcieć używać Guava lub Java9 dla większego bezpieczeństwa.


2

Mając sam ten sam problem, chciałbym opublikować swoją perspektywę. Myślę, że istnieje kilka czynników motywujących do zrobienia czegoś takiego:

  • Chcesz mieć powiązane kody wyliczeniowe, ale w różnych klasach. W moim przypadku miałem klasę podstawową z kilkoma kodami zdefiniowanymi w powiązanym wyliczeniu. W pewnym późniejszym terminie (dziś!) Chciałem zapewnić nową funkcjonalność klasie podstawowej, co oznaczało także nowe kody dla wyliczenia.
  • Klasa pochodna obsługuje zarówno wyliczanie klas podstawowych, jak i własne. Brak zduplikowanych wartości wyliczeniowych! Tak więc: jak uzyskać wyliczenie dla podklasy, która obejmuje wyliczenie jego rodzica wraz z jego nowymi wartościami.

Używanie interfejsu tak naprawdę go nie wycina: możesz przypadkowo uzyskać zduplikowane wartości wyliczeniowe. Nie jest pożądane.

Skończyło się na tym, że połączyłem wyliczenia: to gwarantuje, że nie będzie żadnych zduplikowanych wartości, kosztem mniej ścisłego powiązania z powiązaną klasą. Uznałem jednak, że duplikat jest moim głównym zmartwieniem ...


2

Jako pomoc w zrozumieniu, dlaczego rozszerzenie Enum nie jest rozsądne na poziomie implementacji języka, aby rozważyć, co by się stało, gdybyś przekazał instancję rozszerzonego Enum do procedury, która rozumie tylko podstawowe Enum. Przełącznik obiecany przez kompilator obejmujący wszystkie przypadki w rzeczywistości nie obejmowałby tych rozszerzonych wartości Enum.

To dodatkowo podkreśla, że ​​wartości Java Enum nie są liczbami całkowitymi, takimi jak C, na przykład: aby użyć Java Enum jako indeksu tablicowego, należy jawnie poprosić o jego element ordinal (), aby nadać java Enum dowolną wartość całkowitą, którą należy dodać jawne pole dla tego i odwołanie do tego nazwanego członka.

To nie jest komentarz dotyczący chęci OP, tylko to, dlaczego Java nigdy tego nie zrobi.


1

W nadziei, że to eleganckie rozwiązanie mojego kolegi jest nawet widoczne w tym długim poście, chciałbym podzielić się tym podejściem do podklas, które jest zgodne z podejściem interfejsu i nie tylko.

Pamiętaj, że używamy tutaj niestandardowych wyjątków i ten kod nie zostanie skompilowany, chyba że zastąpisz go wyjątkami.

Dokumentacja jest obszerna i mam nadzieję, że będzie zrozumiała dla większości z was.

Interfejs, który musi implementować każdy podklasowany wyliczanie.

public interface Parameter {
  /**
   * Retrieve the parameters name.
   *
   * @return the name of the parameter
   */
  String getName();

  /**
   * Retrieve the parameters type.
   *
   * @return the {@link Class} according to the type of the parameter
   */
  Class<?> getType();

  /**
   * Matches the given string with this parameters value pattern (if applicable). This helps to find
   * out if the given string is a syntactically valid candidate for this parameters value.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @return <code>true</code> in case this parameter has no pattern defined or the given string
   *         matches the defined one, <code>false</code> in case <code>valueStr</code> is
   *         <code>null</code> or an existing pattern is not matched
   */
  boolean match(final String valueStr);

  /**
   * This method works as {@link #match(String)} but throws an exception if not matched.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @throws ArgumentException with code
   *           <dl>
   *           <dt>PARAM_MISSED</dt>
   *           <dd>if <code>valueStr</code> is <code>null</code></dd>
   *           <dt>PARAM_BAD</dt>
   *           <dd>if pattern is not matched</dd>
   *           </dl>
   */
  void matchEx(final String valueStr) throws ArgumentException;

  /**
   * Parses a value for this parameter from the given string. This method honors the parameters data
   * type and potentially other criteria defining a valid value (e.g. a pattern).
   *
   * @param valueStr <i>optional</i> - the string to parse the parameter value from
   * @return the parameter value according to the parameters type (see {@link #getType()}) or
   *         <code>null</code> in case <code>valueStr</code> was <code>null</code>.
   * @throws ArgumentException in case <code>valueStr</code> is not parsable as a value for this
   *           parameter.
   */
  Object parse(final String valueStr) throws ArgumentException;

  /**
   * Converts the given value to its external form as it is accepted by {@link #parse(String)}. For
   * most (ordinary) parameters this is simply a call to {@link String#valueOf(Object)}. In case the
   * parameter types {@link Object#toString()} method does not return the external form (e.g. for
   * enumerations), this method has to be implemented accordingly.
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getType()}
   */
  String toString(final Object value) throws InternalServiceException;
}

Implementująca klasa bazowa ENUM.

public enum Parameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(new ParameterImpl<String>("VALUE", String.class, "[A-Za-z]{3,10}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

Podklasowane ENUM, które „dziedziczy” po klasie podstawowej.

public enum ExtendedParameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(my.package.name.VALUE);

  /**
   * EXTENDED ENUM VALUE
   */
  EXTENDED_VALUE(new ParameterImpl<String>("EXTENDED_VALUE", String.class, "[0-9A-Za-z_.-]{1,20}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

Wreszcie ogólny ParameterImpl, aby dodać niektóre narzędzia.

public class ParameterImpl<T> implements Parameter {
  /**
   * The default pattern for numeric (integer, long) parameters.
   */
  private static final Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");

  /**
   * The default pattern for parameters of type boolean.
   */
  private static final Pattern BOOLEAN_PATTERN = Pattern.compile("0|1|true|false");

  /**
   * The name of the parameter, never <code>null</code>.
   */
  private final String name;

  /**
   * The data type of the parameter.
   */
  private final Class<T> type;

  /**
   * The validation pattern for the parameters values. This may be <code>null</code>.
   */
  private final Pattern validator;

  /**
   * Shortcut constructor without <code>validatorPattern</code>.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   */
  public ParameterImpl(final String name, final Class<T> type) {
    this(name, type, null);
  }

  /**
   * Constructor.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   * @param validatorPattern - <i>optional</i> - the pattern for {@link #validator}
   *          <dl>
   *          <dt style="margin-top:0.25cm;"><i>Note:</i>
   *          <dd>The default validation patterns {@link #NUMBER_PATTERN} or
   *          {@link #BOOLEAN_PATTERN} are applied accordingly.
   *          </dl>
   */
  public ParameterImpl(final String name, final Class<T> type, final String validatorPattern) {
    this.name = name;
    this.type = type;
    if (null != validatorPattern) {
      this.validator = Pattern.compile(validatorPattern);

    } else if (Integer.class == this.type || Long.class == this.type) {
      this.validator = NUMBER_PATTERN;
    } else if (Boolean.class == this.type) {
      this.validator = BOOLEAN_PATTERN;
    } else {
      this.validator = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    if (null == valueStr) {
      return false;
    }
    if (null != this.validator) {
      final Matcher matcher = this.validator.matcher(valueStr);
      return matcher.matches();
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) throws ArgumentException {
    if (false == this.match(valueStr)) {
      if (null == valueStr) {
        throw ArgumentException.createEx(ErrorCode.PARAM_MISSED, "The value must not be null",
            this.name);
      }
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value must match the pattern: "
          + this.validator.pattern(), this.name);
    }
  }

  /**
   * Parse the parameters value from the given string value according to {@link #type}. Additional
   * the value is checked by {@link #matchEx(String)}.
   *
   * @param valueStr <i>optional</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter:
   *           <ul>
   *           <li>does not {@link #matchEx(String)} the {@link #validator}</li>
   *           <li>cannot be parsed according to {@link #type}</li>
   *           </ul>
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  @Override
  public T parse(final String valueStr) throws ArgumentException, InternalServiceException {
    if (null == valueStr) {
      return null;
    }
    this.matchEx(valueStr);

    if (String.class == this.type) {
      return this.type.cast(valueStr);
    }
    if (Boolean.class == this.type) {
      return this.type.cast(Boolean.valueOf(("1".equals(valueStr)) || Boolean.valueOf(valueStr)));
    }
    try {
      if (Integer.class == this.type) {
        return this.type.cast(Integer.valueOf(valueStr));
      }
      if (Long.class == this.type) {
        return this.type.cast(Long.valueOf(valueStr));
      }
    } catch (final NumberFormatException e) {
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value cannot be parsed as "
          + this.type.getSimpleName().toLowerCase() + ".", this.name);
    }

    return this.parseOther(valueStr);
  }

  /**
   * Field access for {@link #name}.
   *
   * @return the value of {@link #name}.
   */
  @Override
  public String getName() {
    return this.name;
  }

  /**
   * Field access for {@link #type}.
   *
   * @return the value of {@link #type}.
   */
  @Override
  public Class<T> getType() {
    return this.type;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String toString(final Object value) throws InternalServiceException {
    if (false == this.type.isAssignableFrom(value.getClass())) {
      throw new InternalServiceException(ErrorCode.PANIC,
          "Parameter.toString(): Bad type of value. Expected {0} but is {1}.", this.type.getName(),
          value.getClass().getName());
    }
    if (String.class == this.type || Integer.class == this.type || Long.class == this.type) {
      return String.valueOf(value);
    }
    if (Boolean.class == this.type) {
      return Boolean.TRUE.equals(value) ? "1" : "0";
    }

    return this.toStringOther(value);
  }

  /**
   * Parse parameter values of other (non standard types). This method is called by
   * {@link #parse(String)} in case {@link #type} is none of the supported standard types (currently
   * String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param valueStr <i>mandatory</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter cannot be parsed according to {@link #type}
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  protected T parseOther(final String valueStr) throws ArgumentException, InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.parseOther(): Unsupported parameter type: " + this.type.getName());
  }

  /**
   * Convert the values of other (non standard types) to their external form. This method is called
   * by {@link #toString(Object)} in case {@link #type} is none of the supported standard types
   * (currently String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getClass()}
   */
  protected String toStringOther(final Object value) throws InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.toStringOther(): Unsupported parameter type: " + this.type.getName());
  }
}

0

Mój sposób na kodowanie wyglądałby następująco:

// enum A { a, b, c }
static final Set<Short> enumA = new LinkedHashSet<>(Arrays.asList(new Short[]{'a','b','c'}));

// enum B extends A { d }
static final Set<Short> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add((short) 'd');
    // If you have to add more elements:
    // enumB.addAll(Arrays.asList(new Short[]{ 'e', 'f', 'g', '♯', '♭' }));
}

LinkedHashSetzapewnia zarówno, że każdy wpis istnieje tylko raz, i że ich kolejność jest zachowana. Jeśli kolejność nie ma znaczenia, możesz użyć HashSetzamiast tego. Poniższy kod nie jest możliwy w Javie:

for (A a : B.values()) { // enum B extends A { d }
    switch (a) {
        case a:
        case b:
        case c:
            System.out.println("Value is: " + a.toString());
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

Kod można zapisać w następujący sposób:

for (Short a : enumB) {
    switch (a) {
        case 'a':
        case 'b':
        case 'c':
            System.out.println("Value is: " + new String(Character.toChars(a)));
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

Począwszy od Java 7, możesz nawet zrobić to samo z String:

// enum A { BACKWARDS, FOREWARDS, STANDING }
static final Set<String> enumA = new LinkedHashSet<>(Arrays.asList(new String[] {
        "BACKWARDS", "FOREWARDS", "STANDING" }));

// enum B extends A { JUMP }
static final Set<String> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add("JUMP");
}

Korzystanie z zastępowania enum:

for (String a : enumB) {
    switch (a) {
        case "BACKWARDS":
        case "FOREWARDS":
        case "STANDING":
            System.out.println("Value is: " + a);
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.