列挙型をサブクラス化して新しい要素を追加できますか?


534

次のように、既存の列挙型を取り、さらに要素を追加したいと思います。

enum A {a,b,c}

enum B extends A {d}

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

これはJavaで可能ですか?


12
これを行う理由は、コアソースに無効な列挙値を導入せずに無効な列挙値がある状況をテストするためです。
アルキメデストラハノ2013年

はい、「言語学的」純粋さの例。私が望むのは、C ++にあるような整数の自動インクリメントセットの「予約」省力化のアイデアです。これにより、新しいセットを古いセットの拡張として開始し、1 +の最後の値から開始できます。以前のセット、およびエントリに名前が付けられている場合は、「共通サブセット」から名前を継承します。java enumにはいくつかの優れた点がありますが、C ++ enumが提供するヘルプを宣言する単純な自動自動インクリメント整数が欠けています。
2013

4
実際、列挙型を新しい値で拡張すると、サブクラスではなくスーパークラスが作成されます。「拡張」列挙型の代わりにベース列挙値をどこでも使用できますが、その逆はできません。そのため、リスコフ代入原則によれば、拡張列挙型はベース列挙型のスーパークラスです。
Ilya

@Ilya ...はい、そうです。質問には明確な実世界のユースケースがあることを指摘します。議論のために、以下のベース Enumを考えPrimaryColoursます。新しいカラー名を追加して、これをEnum にスーパークラス化するのが妥当ですPrimaryAndPastelColours。リスコフはまだ部屋の象です。:なぜベースの列挙型で始まっていAllMyColoursそして1 - かもしれないが 、サブ:すべての色-classにPrimaryAndPastelColoursし、その後、サブにこの-class: PrimaryColours(心の中で階層を維持)。ただし、Javaはそれを許可しません。
意志

回答:


451

いいえ、Javaではできません。他のものは別としてd、おそらくA(「拡張」の通常の考えが与えられた場合)のインスタンスになりますが、Aません-これは、enumがよく知られているセットであるという点を打ち負かします値。

これの使用方法について詳しく教えていただければ、別の解決策を提案する可能性があります。


516
すべての列挙型は暗黙的にjava.lang.Enumを拡張します。Javaは多重継承をサポートしていないため、列挙型はそれ以外のものを拡張できません。
Givanse

9
私が拡張したい理由は、たとえば、次のようなIntEnumという名前の基本クラスが必要なためです:stackoverflow.com/questions/1681976/enum-with-int-value-in-java/…。その後、すべての列挙型で拡張できます。この場合、継承の恩恵を受けるだけなので、この「intベースの列挙型」コードを頻繁に複製する必要はありません。私はJavaを初めて使用し、C#から来ました。何か不足していることを期待しています。私の現在の意見では、Java列挙型はC#と比較すると苦痛です。
Tyler Collier、

30
@Tyler:C#の列挙型はありません自動検証やで、番号に関連付けられた名前だけですもの。IMO列挙型はJavaの1ビットであり、実際にはC#よりも優れています。
Jon Skeet

21
ここで@JonSkeetに同意しません。私の使用例では、大きな列挙型の厄介なロジックをすべて分離し、ロジックを非表示にし、非表示になっている他の拡張型を拡張するクリーンな列挙型を定義します。多くのロジックを持つ列挙型は、クリーンな変数を宣言するというアイデアに勝るので、何百もの静的文字列変数を宣言する必要がないので、5つの列挙型を持つクラスが判読不能になり、行が大きくなりすぎません。他の開発者にも、次のプロジェクトのためにコードの平和をコピーして貼り付けることに関心を持たせたくないし、代わりにbase_enumを拡張したい...私には理にかなっている...
mmm

43
@givanse ... java.lang.Enumの暗黙の拡張が非継承の原因であるという点であなたに同意しませんjavaのすべてのクラスも暗黙的にObjectクラスを継承しますObject->A->B代わりに階層にObject->A->B extends Object
mickeymoon 2013

317

列挙型は、可能な値の完全な列挙を表します。したがって、(役に立たない)答えはノーです。

実際の問題の例として、平日、週末、および労働組合の曜日を取り上げます。曜日内のすべての日を定義することはできますが、その場合、平日と週末のどちらかに固有のプロパティを表すことができません。

私たちができることは、3つの列挙型を持ち、平日/週末日と曜日の間のマッピングです。

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

または、2つのアプローチを組み合わせることができます。

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ステートメントはインターフェイスでは機能しませんが、通常の列挙型では機能します。スイッチの種類で機能しないと、列挙型の優れた点の1つが終了します。
Manius

9
これには別の問題があるのではないかと思っています。Weekday.MONとDayOfWeek.MONは同じではありません。列挙型のもう1つの大きなメリットではないでしょうか。最善の解決策を見つけようとしているので、これを実現するだけの良い解決策はありません。==を使用できないことは、手を少し強制します。
Snekse、

2
@Crusaderはい、それはまさにトレードオフです。拡張可能なものが必要な場合は、固定のswitchステートメントを使用できません。既知の固定値のセットが必要な場合は、トートロジー上、拡張可能なものがありません。
djechlin

3
列挙型からインターフェイスに移行すると、values()への静的呼び出しも失われます。これにより、特に列挙型を拡張して、確立された列挙型の抽象化バリアとしてインターフェイスを追加する場合は、リファクタリングが困難になります。
Joshua Goldberg

4
インターフェースから列挙型を導出するこのアプローチは、Java 1.7 APIによって使用されます。たとえば、java.nio.file.Files.write()は、最後の引数としてOpenOptionの配列を取ります。OpenOptionはインターフェイスですが、この関数を呼び出すときは、通常、OpenOptionから派生したStandardOpenOption列挙定数を渡します。これには拡張可能という利点がありますが、欠点もあります。実装は、OpenOptionがインターフェースであるという事実に悩まされています。渡された配列からHashSet <OpenOption>を作成します。より効率的で時間効率の高いEnumSetを作成できたはずです。また、スイッチは使用できません。
クリトスキリアクー2015

71

これに対する推奨される解決策は、拡張可能な列挙型パターンですです。

これには、インターフェイスの作成と、現在列挙型を使用している場所でのインターフェイスの使用が含まれます。次に、列挙型にインターフェイスを実装させます。新しい列挙型でインターフェイスを拡張することで、定数をさらに追加できます。


インターフェースでのファクトリーメソッドの使用を呼び出すことは価値があります。拡張が実行可能なソリューションではないことを考えると、関連するEnum間で共通の機能を共有する優れた方法です。
Tim Clemons 2016年

8
このパターンの詳細(コード:))を提供できますか?
Dherik

3
そのパターンでは、列挙型の値を拡張することはできません。質問のどのポイントが尋ねられました。
エリア

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
空の静的ブロックは何をしていますか?'静的{};'
2016年

1
コードは含まれていません。「javap」プログラムは空のブロックを表示します。
ChrisCantrell 2016年

それが何もしていないのにそこにあるのは奇妙ではないですか?
2016年

4
あなたが正しいです!私の間違い。コードの空のブロックではありません。「javap -c」を実行すると、静的ブロック内に実際のコードが表示されます。静的ブロックは、すべてのENUMインスタンス(ここでは1、2、および3)を作成します。申し訳ありません。
ChrisCantrell 2016年

1
真実を述べてくれてありがとう:java.lang.Enumがfinalとして宣言されているからです。
ベンジャミン

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ファイルを変更する必要があります。


興味深いことに、列挙型のtoString()も使用でき、最後に文字列を比較できます。スイッチを使用するには、オブジェクトを既知の列挙型にキャストする必要があります。唯一の問題は、2人の開発者が同じ列挙IDを拡張して作成し、後で両方のコードをマージしようとすることです:)。
アクエリアスパワー

11

見逃した場合のために、すばらしいJoshua Blochの著書「Java Effective、2nd edition」に章があります。

  • 第6章-列挙と注釈
    • 項目34:インターフェイスを使用して拡張可能な列挙型をエミュレートする

ここで抽出します

ただ結論:

拡張可能な列挙型をエミュレートするためにインターフェースを使用することの小さな欠点は、実装をある列挙型から別の列挙型に継承できないことです。操作の例の場合、操作に関連付けられたシンボルを保存および取得するロジックは、BasicOperationとExtendedOperationで重複しています。この場合、コードがほとんど複製されないため、問題になりません。共有機能の量が多い場合は、ヘルパークラスまたは静的ヘルパーメソッドにカプセル化して、コードの重複を排除できます。

要約すると、拡張可能な列挙型を作成することはできませんが、インターフェースを実装する基本的な列挙型に対応するインターフェースを作成することで、それをエミュレートできます。これにより、クライアントはインターフェースを実装する独自の列挙型を作成できます。これらの列挙型は、APIがインターフェースの観点から記述されていると想定して、基本的な列挙型を使用できる場所であればどこでも使用できます。


6

enumは拡張できないため、列挙は避けます。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
インスタンスの序数だけが必要な場合は問題ないかもしれません。しかし、列挙型はかなり便利な名前プロパティも持っています。
2018年

6

これは、静的初期化子のランタイムチェックで列挙型継承パターンを拡張する方法です。BaseKind#checkEnumExtender「延びる」は、列挙型がまったく同じ方法でベース列挙のすべての値を宣言していることをチェックするように#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に基づいて-スイッチのサポートを追加するタックライン回答、

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 2017

@AxelAdventoここでの考え方Dayは、メソッドvalueOf()then を持つインターフェースに依存して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");
}

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.");
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.