読者です 読者をやめる 読者になる 読者になる

【Effective Java】項目34:拡張可能な enum をインタフェースで模倣する

Enum の疑似拡張

JavaEnum 型は拡張することはできません。 たとえ可能だとしても拡張するべきではありません。 ただし、拡張可能な Enum を利用したくなる場合があります。

オペコードと呼ばれるマシンに対する操作を列挙型で表現したいとします。 そのような場合には、API ユーザーが固有の操作を実装できるようにすることは望ましいことです。

このような場合には、Enum がインタフェースを実装できるという点を利用して、擬似的に Enum を拡張します。 これを Enum の疑似拡張と呼びます。

以下では Operation というインタフェースを宣言し、その標準実装を BasicOperation という Enum として定義しています。

interface Operation {
    double apply(double x, double y);
}

public enum BasicOperation implements Operation {
    PLUS("+") {
        @Override public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        @Override public double apply(double x, double y) {
            return x - y;
        }
    };

    private final String symbol;
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }
}

BasicOperation は Enum であるため拡張できませんが、インタフェース型は拡張可能です。 Operaiton インタフェースを利用し、 カスタマイズされた Operation を新たに定義することができます。

public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x  % y;
        }
    };

    private final String symbol;
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
}

疑似拡張は実際の拡張とは異なるため、BasicOperation の動作を継承することはできません。 また、拡張できないので継承によるコードの重複を避ける方法は使えません。

もし、コードの重複を避けたい場合には static のヘルパーメソッドなどを作ってコードの重複を除去しましょう。

疑似拡張 Enum の定数の列挙

疑似拡張 Enum を利用する場合、メソッドの引数にはインタフェースで宣言したほうが望ましいです。

Enumメソッド引数の宣言としていれば、すべての定数を列挙する方法は簡単です。 単に Enum.values() を用いればよいです。

引数として実装したインタフェースをうけとるようにすると、すべての定数を列挙する方法は少し難しくなりますが、不可能ではありません。 Class オブジェクトの getEnumConstants() を使う方法です。

http://docs.oracle.com/javase/jp/7/api/java/lang/Class.html#getEnumConstants()

このメソッドは Class オブジェクトが Enum 型を表していた場合に、すべての定数の配列を返します。 もし、Enum 型を表さない場合には null が返ります。

このメソッドを使ってすべての定数を列挙するコードは以下の様になります。

private static <T extends Enum<T> & Operation> void getAllConstants(
            Class<T> opSet) {
    for (Operation op : opSet.getEnumConstants()) {
        System.out.printf("Operation: %s%n", op);
    }
}

このメソッドは引数として Class<T> を受け取っていますが、その T には以下のような制限がかけられています。

  • Enum を継承していること(= Enum であること)
  • Operation を継承していること

この制約によって、Operation を実装し、Enum であることが保障されるので getEnumConstants() が null を返すことはありません。

感想

このようなテクニックは面白いのだけど、実際使う機会あるかなーという感じがする。 ライブラリや SDK を提供するような場合に使うかもしれない。

次回からはアノテーション