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

大阪EffectiveJava読書会 第1回

本日、初めての読書会として大阪で行われたEffectiveJava読書会 第1回に行ってきました。
一言に読書会と言っても、一人ずつ音読したり、各々が黙々と読んで質問したりすると色々とスタイルがあるらしく、今回の私が参加したこの読書会は、3人のチーム*4組に別れてEffectiveJavaの各章どれか一つについて1時間程度でまとめて発表するというスタイルでした。
そんなわけで私は主催者の@irofさん、@meganiiさんと共に第六章、主に項目30のenumについて発表したので、そのあたりを中心にまとめました。

  • 項目30 int定数の代わりにenumを使用する

javaにおけるenumの大きな特徴として、主に次の2点が挙げられます。

  1. 型安全性の保証
  2. データ・振る舞いを持つことができる

まずは型安全性の保障の保証について。

enumが実装されるJava1.4以前ではint enumパターンと呼ばれる技法が使用されていました。

//int enumパターン
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

このパターンをにはどのような問題があるかというと、定数値がただのint型であるために異なるドメインの定数値を誤って混在させてしまっても、コンパイラがこの誤りを検出することが出来ません。

//example
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVAL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;

//コンパイル可能
private int apple = ORANGE_NAVAL;


一方、enumを使用することでこのような誤りをコンパイラによって検出することが可能になり、型安全性が保証されます。

//example
public enum Apple{ FUJI, PIPIN, GRANNY_SMITH };
public enum Orange{ NAVEL, TEMPLE, BLOOD };

//コンパイルエラー!
public Apple apple = Orange.NAVAL;


static importを使用すると頭のOrangeやAppleを省略できるため、int enumパターンと同じように記述できます。

import static Apple.*;

public Apple apple = FUJI;


次に、javaenum型はC言語C++enumとは異なり、データ・振る舞いを持つことが可能です。

//データを持つenum
public enum Operation{
    PLUS( "+" ),
    MINUS( "-" );
    private String symbol;
    Operation( String symbol ){ this.symbol = symbol };
    @override
    public String toString(){ return symbol };
}
//振る舞いを持つenum
public enum Operation{
    PLUS { double apply( double x, double y ){ return x + y;}},
    MINUS { double apply( double x, double y ){ return x - y;}};
    abstract double apply( double x, double y );
}

このようにすることでswitch文を使用することなく定数と紐づいたメソッドを呼ぶことができます。まあストラテジパターンの簡単な実装といった所ですね。

//example
public double apply( double x, double y, Operation operation ){
    return operation.apply( x, y );
}


また、enum自身に振る舞いが実装されている場合、定数値が増えた場合にも switchブロックの修正も不要になります。

//振る舞いを持つenum
public enum Operation{
    PLUS { double apply( double x, double y ){ return x + y;}},
    MINUS { double apply( double x, double y ){ return x - y;}},
    TIMES { double apply( double x, double y ){ return x - y;}},
    DIVEDE { double apply( double x, double y ){ return x - y;}};
    abstract double apply( double x, double y );
}

//TIMESとDIVEDEが増えたけど変更しなくて良い
public double apply( double x, double y, Operation operation ){
    return operation.apply( x, y );
}

上のようなenum内で定義されたabstractメソッドの場合、実装しなければコンパイルエラーになるため、実装し忘れることはありません。
しかし、switch文による分岐で実装した場合、定数に対する実装を忘れてしまってもコンパイラは知らせてくれません。

//caseブロックを追加し忘れてもコンパイルは通る
public double apply( double x, double y, Operation operation ){
    switch( operation ){
        case PLUS: return x + y;
        case MINUS: return x - y;
    }
}

ただ、メソッドを定義できるのが便利と言っても、大量の処理を突っ込むなどのやり過ぎは良くないんじゃないかという話も。
そういう場合は素直にインターフェイスを実装したクラスに分ければいいのではないかと思います。


とまあ、発表では後半の具体的なコード*1まで出せませんでしたが、大体こんなような話だったような気がします。
このように便利なenum、どんどん使っていってもらいたいものです。

しかし

  • javaのバージョンがいまだに1.4
  • 誰もenum使ってない
  • enumを使った他人の分からないコードを書いちゃだめ
  • enumはコード規約で禁止

という話も・・・。

*1:このエントリの物は主にEffectiveJavaより抜粋