HOWTO · Java

Java で列挙型を拡張する

このチュートリアルでは、Java で enum 機能を拡張する方法を示します。

このチュートリアルでは、Java で enum 機能を拡張する方法を示します。

Java で enum を拡張する

enum は一種のコンパイラ マジックと見なすことができます。これは、byte コードでは、enum がいくつかの static メンバーを持つクラスとして表され、抽象 java.lang.Enum から継承されているためです。

これが、enum が他のクラスまたは enum を拡張できない理由です。 Java では enum を拡張できないため、どのクラスでも enum を拡張することはできません。 次のコード例を使用して学習し、何が起こるか見てみましょう。

コード例:

package delftstack;

enum Cars { Audi, BMW, Marcedes }

public class Example extends Cars {
  public static void main(String... args) {}
}

上記のコードには Cars という名前の enum があり、クラス Example はそれを拡張しようとしています。 出力を参照してください:

/Example.java:5: error: cannot inherit from final Cars
public class Example extends Cars {
                             ^
/Example.java:5: error: enum types are not extensible
public class Example extends Cars {
       ^
2 errors

ご覧のとおり、クラスは enum を拡張できません。 enum を拡張することが不可能な場合でも、その機能を拡張することはできますか?

機能は、enum で実装されたメソッドとして定義できます。 たとえば、上記のコードの enum Cars は、各メンバーの抽象メソッドを宣言できます。 例を参照してください。

enum Cars {
  Audi {
    @Override
    public void drive() {}
  },
  BMW {
    @Override
    public void drive() {}
  },
  Mercedes {
    @Override
    public void drive() {}
  },
  ;
  public abstract void drive();
}

ご覧のとおり、各メンバーは drive() メソッドをオーバーライドできます。 しかし残念なことに、常に enum でメソッドを作成できるとは限りません:

  • enum がサードパーティ ライブラリまたは別のチームに属している場合、抽象メソッドの実装は許可されません。
  • drive() メソッドに必要な依存関係を持たないモジュールに属している場合。
  • enum が他の関数やデータでオーバーロードされている場合、読み取り不能になります。

これらの問題を解決し、Java の enum の機能を拡張できるソリューションがいくつか提供されています。

解決策 1: Java で enum をミラーリングする

名前が示すように、同じデータを持つ別の enum を作成する必要があります。 その新しい enumdrive() メソッドも実装するため、現在 2つの列挙型があります。

enum Cars のコード例:

enum Cars {
  Audi {
    @Override
    public void drive() {}
  },
  BMW {
    @Override
    public void drive() {}
  },
  Mercedes {
    @Override
    public void drive() {}
  },
  ;
  public abstract void drive();
}

enum DriveCars のコード例:

enum DriveCars {
  Audi {
    @Override
    public void drive() {}
  },
  BMW {
    @Override
    public void drive() {}
  },
  Mercedes {
    @Override
    public void drive() {}
  },
  ;
  public abstract void drive();
}

2 番目の enum は最初のもののミラーです。 機能を拡張することで、これらの列挙型の両方を使用できるようになりました。 name()valueof() である組み込みの enum メソッドを使用する必要があります。

使用方法については、次の例を参照してください。

Cars cars = ... DriveCars.valueOf(cars.name()).drive();

上記のコードは、enum DriveCarsenum Cars 機能がどのように使用されるかを示しています。 つまり、機能が拡張されます。

上記のコードの name() メソッドは final であり、オーバーライドすることはできず、valueOf メソッドはコンパイラによって生成されます。 これらの方法は両方とも、拡張操作に機能上のエラーがない場合、互いにうまく適合します。

enum Cars が変更された場合、上記のコードには 1つの問題があり、enum DriveCars は何も認識せず、namevalueof トリックの失敗を引き起こします。 この問題を解決するには、親ミラーがCarsであることをDriveCarsに知らせる必要があります。

そのために、static イニシャライザを使用して DriveCarsCars を比較できます。これは、両方の列挙型が一致しない場合に例外をスローします。 enumus ライブラリの例を次に示します。

enum DriveCars {
  ....static { Mirror.of(Cars.class); }
}

ユーティリティ クラスは、両方の列挙型が一致するかどうかをチェックします。 このメソッドは、name() および valueOf() トリックを検証します。

解決策 2: Java で enum をマップする

メソッドを 1つだけ保持する別の enum を作成したくない場合。 この場合、enum の代わりに interface を使用できます。 以下の例を参照してください。

public interface Driver {
  void drive();
}

この interface Driveenum Cars で使用するために、それらの間のマッピングを作成できます。 マップの例を次に示します。

Map<Cars, Driver> drivers = new EnumMap<>(Cars.class) {
  {
    put(Audi, new Driver() {
      @Override
      public void driver() {}
    }) put(BMW, new Driver() {
      @Override
      public void driver() {}
    }) put(Mercedes, new Driver() {
      @Override
      public void driver() {}
    })
  }
}

それらを使用するには、次の簡単なコードを使用します。

drivers.get(Cars).drive();

上記のコードで使用されている EnumMap は、各 enum メンバーが 1 回だけ表示されることを保証しますが、各メンバーのエントリは保証しません。

マップのサイズが列挙型のメンバーの数と等しいことを確認できます。

drivers.size() == Cars.values().length

enumus ライブラリは、この場合のユーティリティも提供します。マップが Cars に適合しない場合、IllegalStateException がスローされます。 ユーティリティは次のとおりです。

EnumMapValidator.validateValues(Cars.class, map, "Cars map");

上記の両方の方法は、機能を拡張して列挙型を強力にする方法を示しています。 enum を直接拡張することは不可能ですが、これらのトリックを使用して機能を拡張できます。