새로운 요소를 추가하기 위해 열거 형을 서브 클래스 화 할 수 있습니까?


534

기존 열거 형을 가져 와서 다음과 같이 더 많은 요소를 추가하고 싶습니다.

enum A {a,b,c}

enum B extends A {d}

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

Java에서 가능합니까?


12
이를 수행하는 이유는 코어 소스에 유효하지 않은 열거 형 값을 도입하지 않고 유효하지 않은 열거 형 값이있는 상황을 테스트하기위한 것입니다.
Archimedes Trajano

"언어 적"순도의 예입니다. C ++에서와 같이 정수의 자동 증분 세트에 대한 "부기 유지"노동 절약 아이디어가 필요하다고 생각합니다. 따라서 이전 세트의 확장으로 새 세트를 시작할 수 있습니다. 이전 세트 및 항목의 이름이 "공통 서브 세트"에서 이름을 상속합니다. Java 열거 형에는 좋은 점이 있지만 C ++ 열거 형이 제공하는 간단한 자동 자동 증가 정수 선언 도움말이 없습니다.
peterk

4
실제로 새로운 값으로 열거 형을 확장하면 하위 클래스가 아니라 슈퍼 클래스가 생성됩니다. Liskov 대체 원칙에 따르면 확장 열거 형은 기본 열거 형의 슈퍼 클래스 인 "확장 된"열거 형 대신 어디에서나 기본 열거 형 값을 사용할 수 있습니다.
Ilya

@Ilya ... 그렇습니다. 나는 그 질문에 실제 실제 사용 사례가 있다고 지적한다. 인수를 위해 다음과 같은 기본 Enum을 고려 하십시오 PrimaryColours. 새로운 색 이름을 추가하여 이것을 Enum 에 슈퍼 클래스로 만들고자 하는 것이 합리적 PrimaryAndPastelColours입니다. Liskov는 여전히 방에있는 코끼리입니다. : 왜베이스의 열거로 시작하지 AllMyColours그리고 하나 - 힘의 하위 : 모든 색상 - 클래스에 PrimaryAndPastelColours이어서 하위 이 -class : PrimaryColours(마음에 계층 구조를 유지). Java도 그렇게 허용하지 않습니다.
것이다

답변:


450

아니요, Java에서는이 작업을 수행 할 수 없습니다. 다른 것 외에도, d아마도 A( "확장자"라는 일반적인 아이디어가 주어 졌을 것임) 인스턴스 일 것입니다 . 그러나 오직 알고있는 사용자는 그것에 대해 알지 못했을 A것입니다. 가치.

사용 방법에 대해 더 자세히 알려면 대체 솔루션을 제안 할 수 있습니다.


516
모든 열거 형은 암시 적으로 java.lang.Enum을 확장합니다. Java는 다중 상속을 지원하지 않으므로 열거 형은 다른 것을 확장 할 수 없습니다.
givanse

9
내가 확장하고 싶은 이유는 IntEnum과 같은 기본 클래스를 가지고 있기 때문입니다. stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . 그런 다음 모든 열거 형이 확장 할 수 있습니다 ...이 경우 상속의 이점을 얻으 므로이 "int-based enum"코드를 자주 복제 할 필요가 없습니다. Java에 익숙하지 않고 C #에서 왔으며 뭔가 빠졌기를 바라고 있습니다. 내 현재 의견은 Java 열거 형이 C #에 비해 고통 스럽다는 것입니다.
Tyler Collier

30
@Tyler : C #을 열거없이 자동으로 검증 또는으로, 숫자와 관련된 단지 이름이다 아무것도 . IMO 열거 형은 실제로 C #보다 나은 1 비트 Java입니다.
Jon Skeet

21
@JonSkeet과 동의하지 않습니다. 내 유스 케이스에서는 큰 열거 형의 모든 불쾌한 논리를 분리하고 논리를 숨기고 숨겨진 다른 열거 형을 확장하는 깨끗한 열거 형을 정의하고 싶습니다. 많은 논리를 가진 열거 형은 깨끗한 변수를 선언한다는 아이디어보다 뛰어납니다. 따라서 수백 개의 정적 문자열 변수를 선언 할 필요가 없으므로 5 개의 열거 형을 가진 클래스는 읽을 수 없으며 너무 커지지 않습니다. 나는 다른 개발자들이 다음 프로젝트를 위해 코드의 평화를 복사하고 붙여 넣는 것에 관심을 갖기를 원치 않고 대신 base_enum을 확장하는 것이 좋습니다.
mmm

43
@givanse ... java.lang.Enum의 암시 적 확장의 관점에서 당신에게 동의하지 않습니다 .Enum의 모든 클래스는 암시 적으로 Object 클래스를 상속하지만 다른 클래스를 상속받을 수 있으므로 상속되지 않는 원인이됩니다. Object->A->B대신에 계층 구조로Object->A->B extends Object
mickeymoon

317

열거 형은 가능한 값의 완전한 열거 형을 나타냅니다. 따라서 (도움이되지 않는) 대답은 '아니요'입니다.

실제 문제의 예로는 주중, 주말 및 노조가 있습니다. 요일 내에 모든 요일을 정의 할 수 있지만 주중과 주말에 특별한 속성을 나타낼 수는 없습니다.

우리가 할 수있는 일은 주중 / 주말과 요일 사이에 매핑되는 세 가지 열거 형 유형이 있습니다.

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;
}

또는 요일에 대한 개방형 인터페이스를 사용할 수도 있습니다.

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

또는 두 가지 접근 방식을 결합 할 수 있습니다.

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
이것에 문제가 없습니까? switch 문은 인터페이스에서 작동하지 않지만 일반 열거 형에서는 작동합니다. 스위치를 사용하지 않으면 열거 형에 대한 좋은 점 중 하나가 죽습니다.
마니 우스

9
나는 이것에 또 다른 문제가있을 것이라고 생각합니다. Weekday.MON과 DayOfWeek.MON은 동일하지 않습니다. 열거 형의 다른 큰 이점이 아닌가요? 더 나은 해결책이 없으며 최상의 답변을 찾으려고 노력하면서 이것을 실현합니다. ==를 사용할 수없는 것은 손을 약간 강요합니다.
Snekse

2
@Crusader 네, 정확히 절충점입니다. 확장 가능한 것을 원하면 고정 된 switch 문을 가질 수 없으며 고정 된 알려진 알려진 값 집합을 원한다면 확장 가능한 것을 가질 수 없습니다.
djechlin

3
열거 형에서 인터페이스로 이동하면 values ​​()에 대한 정적 호출도 손실됩니다. 특히 열거 형을 확장하고 설정된 열거 형에 대한 추상화 장벽으로 인터페이스를 추가하려는 경우 리팩토링이 어렵습니다.
조슈아 골드버그

4
인터페이스에서 열거 형을 가져 오는이 접근 방식은 Java 1.7 API에서 사용됩니다. 예를 들어 java.nio.file.Files.write ()는 마지막 인수로 OpenOption 배열을 사용합니다. OpenOption은 인터페이스이지만이 함수를 호출하면 일반적으로 OpenOption에서 파생 된 StandardOpenOption 열거 형 상수를 전달합니다. 이는 확장 가능하다는 장점이 있지만 단점도 있습니다. OpenOption이 인터페이스라는 사실로 인해 구현에 어려움이 있습니다. 더 공간적이고 시간 효율적인 EnumSet을 만들 수있을 때 전달 된 배열에서 HashSet <OpenOption>을 만듭니다. 그리고 스위치를 사용할 수 없습니다.
Klitos Kyriacou

71

이에 대한 권장 솔루션은 확장 가능한 열거 형 패턴 입니다.

여기에는 인터페이스를 만들고 현재 열거 형을 사용하는 인터페이스를 사용하는 것이 포함됩니다. 그런 다음 열거 형이 인터페이스를 구현하도록하십시오. 새 열거 형도 인터페이스를 확장하여 상수를 더 추가 할 수 있습니다.


인터페이스에서 팩토리 메소드 사용을 호출하는 것이 좋습니다. 확장이 실행 가능한 솔루션이 아니라면 관련 Enum간에 공통 기능을 공유하는 좋은 방법입니다.
Tim Clemons

8
이 패턴에 대한 자세한 정보 (코드 :)를 제공 할 수 있습니까?
Dherik

3
이 패턴은 열거 형 값을 확장 할 수 없습니다. 질문의 요점은 어느 것입니까?
Eria

55

커버 아래에서 ENUM은 컴파일러가 생성 한 일반 클래스입니다. 생성 된 클래스가 확장됩니다 java.lang.Enum. 생성 된 클래스를 확장 할 수없는 기술적 이유는 생성 된 클래스가 final입니다. 최종적인 개념적 이유는이 주제에서 설명합니다. 그러나 토론에 역학을 추가하겠습니다.

다음은 테스트 열거 형입니다.

public enum TEST {  
    ONE, TWO, THREE;
}

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);
}

아마도이 클래스를 직접 입력하고 "최종"을 삭제할 수 있습니다. 그러나 컴파일러는 "java.lang.Enum"을 직접 확장하지 못하게합니다. java.lang.Enum을 확장하지 않기로 결정할 수 있지만 클래스와 파생 클래스는 java.lang.Enum의 인스턴스가 아니므로 실제로는 중요하지 않을 수 있습니다!


1
빈 정적 블록은 무엇을하고 있습니까? '정적 {};'
soote

1
코드가 없습니다. "javap"프로그램은 빈 블록을 보여줍니다.
ChrisCantrell

아무것도하지 않으면 거기에있는 것이 이상하지 않습니까?
soote

4
당신이 맞아요! 내 실수. 빈 코드 블록이 아닙니다. "javap -c"를 실행하면 정적 블록 안에 실제 코드가 표시됩니다. 정적 블록은 모든 ENUM 인스턴스를 생성합니다 (여기서는 1, 2, 3). 미안합니다.
ChrisCantrell

1
java.lang.Enum이 final로 선언되었으므로 정확한 사실을 알려 주셔서 감사합니다.
Benjamin

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

다음과 같이 쓸 수 있습니다 :

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • ClassGroup.B.getMembers () 는 {a, b, c, d}를 포함합니다

유용한 방법 : 다음과 같은 것을 원한다고 가정 해 봅시다. 이벤트가 있고 열거 형을 사용하고 있습니다. 이러한 열거 형은 유사한 처리로 그룹화 할 수 있습니다. 요소가 많은 작업이있는 경우 일부 이벤트는 작업을 시작하고 일부는 단계적이며 다른 작업은 종료됩니다. 이러한 작업을 수집하고 긴 스위치 케이스를 피하기 위해 예제와 같이 그룹화하고 다음을 사용할 수 있습니다.

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

예:

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      
}  

좀 더 고급 추가 :

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),
(...)

위에서 우리가 실패한 경우 (myEvent.is (State_StatusGroup.FAIL)) 이전 이벤트를 반복하면 다음과 같이 송금을 되돌려 야하는지 쉽게 확인할 수 있습니다.

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

다음에 유용 할 수 있습니다.

  1. 처리 논리에 관한 명시 적 메타 데이터 포함
  2. 다중 상속의 일부 구현
  3. 우리는 클래스 구조를 사용하고 싶지 않습니다. 예. 짧은 상태 메시지 보내기

13

다음은 열거 형을 다른 열거 형으로 확장하는 방법을 찾은 방법입니다. 매우 성가신 접근 방식입니다.

공통 상수가있는 열거 형이 있다고 가정하십시오.

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;
    }
}

다음과 같이 수동 확장을 시도 할 수 있습니다.

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;
    }
}

물론 상수를 확장해야 할 때마다 SubEnum 파일을 수정해야합니다.


흥미롭게도 우리는 매우 enum toString ()을 사용할 수 있으며 결국 문자열을 비교할 수 있습니다. 스위치를 사용하려면 객체를 알려진 열거 형으로 캐스팅해야합니다. 유일한 문제는 2 명의 개발자가 동일한 열거 형 ID를 확장하고 생성하고 나중에 두 코드를 병합하려고 시도하는 것입니다.
물병 자리 힘

11

당신이 그것을 놓친 경우, 훌륭한 Joshua Bloch의 저서 " Java Effective, 2nd edition "에 장이 있습니다.

  • 6 장 열거 형과 주석
    • 항목 34 : 인터페이스를 사용하여 확장 가능한 열거 형 에뮬레이션

여기에서 추출 하십시오 .

그냥 결론 :

확장 가능한 열거 형을 에뮬레이트하기 위해 인터페이스를 사용하는 것의 단점은 구현이 하나의 열거 형에서 다른 열거 형으로 상속 될 수 없다는 것입니다. Operation 예제의 경우 오퍼레이션과 연관된 기호를 저장하고 검색하는 로직이 BasicOperation 및 ExtendedOperation에 복제됩니다. 이 경우 코드가 거의 복제되지 않으므로 중요하지 않습니다. 더 많은 양의 공유 기능이있는 경우이를 도우미 클래스 또는 정적 도우미 메서드에 캡슐화하여 코드 중복을 제거 할 수 있습니다.

요약하면 확장 가능한 열거 형 유형을 작성할 수는 없지만 인터페이스를 구현하는 기본 열거 형 유형에 맞는 인터페이스를 작성하여 에뮬레이션 할 수 있습니다. 이를 통해 클라이언트는 인터페이스를 구현하는 자체 열거 형을 작성할 수 있습니다. 그런 다음 API가 인터페이스 측면에서 작성되었다고 가정하면 기본 열거 형 유형을 사용할 수있는 모든 위치에서이 열거 형을 사용할 수 있습니다.


6

나는 열거 할 수 없기 때문에 열거 형을 피하는 경향이 있습니다. OP의 예를 유지하기 위해 A가 라이브러리에 있고 B가 자신의 코드에 있으면 A가 열거 형이면 A를 확장 할 수 없습니다. 이것은 때때로 열거 형을 대체하는 방법입니다.

// 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; };
}

피해야 할 구덩이가 있습니다. 코드의 주석을 참조하십시오. 필요에 따라 열거 형에 대한 견고하고 확장 가능한 대안입니다.


1
예를 들어 서 수가 필요하면 좋을 수도 있습니다. 그러나 열거 형은 또한 매우 유용한 이름 속성을 가지고 있습니다.
inor

6

이것이 정적 초기화 프로그램에서 런타임 검사로 열거 상속 패턴을 향상시키는 방법입니다. BaseKind#checkEnumExtender"연장"이 ENUM이 동일하게베이스 ENUM의 모든 값을 선언하는 것이 확인하므로 #name()#ordinal() 완벽하게 호환 남아있다.

값을 선언하는 데 여전히 복사-붙여 넣기가 필요하지만 누군가가 기본 값을 확장하거나 업데이트하지 않고 값을 추가하거나 수정하면 프로그램이 빠르게 실패합니다.

서로 다른 열거 형에 대한 일반적인 동작은 서로 확장됩니다.

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() ;
}

확인 방법이있는 기본 열거 형 :

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 ) ;
    }
  }

}

확장 샘플 :

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

@Tom Hawtin-tackline 답변을 기반으로 스위치 지원을 추가합니다.

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:
}

valueOf()방법 의 용도는 무엇입니까 ?
Axel Advento

생각이 여기에 우리가 인터페이스에 의존하다 @AxelAdvento Day방법을 가지고 valueOf()다음 switch(Day.valueOf()), 그것은에 의해 구현 것 WeekDay, WeekEndDay열거.
Khaled Lela

3

나는 당신이 다른 방법으로 접근하는 것이 좋습니다.

기존 열거를 확장하는 대신 더 큰 열거 형을 만들고 하위 집합을 만듭니다. 예를 들어 PET라는 열거 형이 있고이를 ANIMAL로 확장하려는 경우 대신 다음을 수행해야합니다.

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

애완 동물은 불변 컬렉션이 아니므로 더 안전을 위해 Guava 또는 Java9를 사용할 수 있습니다.


2

이 같은 문제가 발생하여 내 관점을 게시하고 싶습니다. 나는 이와 같은 일을하기 위해 몇 가지 동기 부여 요소가 있다고 생각합니다.

  • 관련된 열거 형 코드를 갖고 싶지만 다른 클래스에 있습니다. 내 경우에는 관련 열거 형에 정의 된 여러 코드가있는 기본 클래스가 있습니다. 나중에 (오늘!) 나는 기본 클래스에 새로운 기능을 제공하고 싶었습니다. 이는 열거 형에 새로운 코드를 의미했습니다.
  • 파생 클래스는 기본 클래스의 열거 형과 자체 클래스를 모두 지원합니다. 중복 된 열거 형 값이 없습니다! 그래서 : 새로운 값과 함께 부모의 열거 형을 포함하는 서브 클래스에 대한 열거 형을 얻는 방법.

인터페이스를 사용해도 실제로 잘릴 수는 없습니다. 실수로 중복 된 열거 형 값을 얻을 수 있습니다. 바람직하지 않습니다.

나는 열거 형을 결합하게되었습니다. 이렇게하면 관련 클래스에 덜 밀접하게 연결되는 대신 중복 값이 ​​존재할 수 없습니다. 그러나 중복 문제가 내 주요 관심사라고 생각했습니다 ...


2

확장 Enum의 인스턴스를 기본 Enum 만 이해하는 루틴으로 전달하면 어떻게 될지 고려하기 위해 언어 구현 레벨에서 Enum을 확장하는 것이 왜 합리적이지 않은지 이해하는 데 도움이됩니다. 컴파일러가 모든 경우를 다룰 것이라고 약속 한 스위치는 실제로 확장 된 Enum 값을 다루지 않습니다.

또한 Java Enum 값은 C와 같은 정수가 아님을 강조합니다. 예를 들어 Java Enum을 배열 인덱스로 사용하려면 ordinal () 멤버를 명시 적으로 요청해야하며 Java Enum에 추가해야하는 임의의 정수 값을 제공해야합니다. 그것에 대한 명시 적 필드와 그 명명 된 멤버를 참조하십시오.

이것은 OP의 요구에 대한 의견이 아니며, 왜 Java가 절대 그렇게하지 않을지에 대한 의견입니다.


1

내 동료 의이 우아한 솔루션 이이 긴 게시물에서도 볼 수 있기를 바랍니다. 하위 인터페이스 접근 방식과 그 이후의 서브 클래스 링에 대한이 접근법을 공유하고 싶습니다.

여기서는 사용자 정의 예외를 사용하며이 코드를 예외로 바꾸지 않으면이 코드가 컴파일되지 않습니다.

설명서는 광범위하며 대부분의 사람들이 이해할 수 있기를 바랍니다.

모든 서브 클래스 열거 형이 구현해야하는 인터페이스입니다.

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;
}

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);
  }
}

기본 클래스에서 "상속되는"하위 클래스 ENUM

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);
  }
}

마지막으로 일반 ParameterImpl을 사용하여 일부 유틸리티를 추가하십시오.

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

코딩하는 방법은 다음과 같습니다.

// 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', '♯', '♭' }));
}

LinkedHashSet각 항목이 한 번만 존재하며 순서가 유지되도록합니다. 순서가 중요하지 않으면 HashSet대신 사용할 수 있습니다 . Java 에서는 다음 코드를 사용할 수 없습니다 .

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.");
    }
}

코드는 다음과 같이 작성할 수 있습니다.

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.");
    }
}

Java 7부터는 다음과 같이 할 수도 있습니다 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");
}

열거 형 교체 사용 :

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.");
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.