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

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

Java Effective Java

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 を提供するような場合に使うかもしれない。

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