typesafe enum講座
海藻猫的ななにか(@vvakame)とTwitterでやり取りしたので、まとめるかねぇ?と現実逃避的な何か<そんな時間ないだろJK
Javaには5.0から列挙型(enum)が導入されています。
C/C++やC#のenumと異なり、主に以下の特徴があります。
- typesafe enumである
- 実体は列挙値ごとに、定数オブジェクトである
- したがって、==による比較ができる
- インタフェースのimplementsやstatic/instanceメソッドの追加、メンバ変数などを定義できる
- annotationに値として指定できる
初歩的なenumの使い方
例えばResultSetから値を取得する場合、その型に応じた取得をしないといけませんが、これをenumに隠ぺいすることができます。
public enum SQLiteTypeEnum { TYPE_INTEGER(Integer.class) , TYPE_FLOAT(Float.class) , TYPE_STRING(String.class) , TYPE_OBJECT(Object.class); public static SQLiteTypeEnum get(Class<?> klass) { for (SQLiteTypeEnum value : values()) { if (value.type != klass) continue; return value; } throw new IllegalArgumentException("unsupported class"); } private Class<?> type; private SQLiteTypeEnum(Class<?> type) { this.type = type; } public Object getValue(ResultSet rs, String columnName) throws SQLException { Object v = null; switch (this) { case TYPE_INTEGER: { v = rs.getInt(columnName); break; } case TYPE_FLOAT: { v = rs.getFloat(columnName); break; } case TYPE_STRING: { v = rs.getString(columnName); break; } case TYPE_OBJECT: { v = rs.getObject(columnName); break; } } return v; } }
使う側からは、
SQLiteTypeEnum.get(String.class).getValue(rs, "hoge");
などという形にできます。
これにより、定型処理をenumに押し込めることができ、ビジネスロジック側はスッキリしたコードになります。
1歩進んだenumの使い方
enumは上述のとおり、interfaceのimplementsができるので、上記コードを複数のデータベース対応にすることも比較的容易です。
public interface IType { public Object getValue(ResultSet rs, String columnName) throws SQLException; } public enum TypeFactory { SQLITE, HSQLDB; public IType get(Class<?> type) { switch (this) { case SQLITE: { return SQLiteTypeEnum.get(type); } case HSQLDB: { return HSQLDBTypeEnum.get(type); } throw new IllegalArgumentException("unsupported database"); } } public enum SQLiteTypeEnum implements IType { TYPE_INTEGER(Integer.class) , TYPE_FLOAT(Float.class) , TYPE_STRING(String.class) , TYPE_OBJECT(Object.class); public static SQLiteTypeEnum get(Class<?> klass) { for (SQLiteTypeEnum value : values()) { if (value.type != klass) continue; return value; } throw new IllegalArgumentException("unsupported class"); } private Class<?> type; private SQLiteTypeEnum(Class<?> type) { this.type = type; } public Object getValue(ResultSet rs, String columnName) throws SQLException { Object v = null; switch (this) { case TYPE_INTEGER: { v = rs.getInt(columnName); break; } case TYPE_FLOAT: { v = rs.getFloat(columnName); break; } case TYPE_STRING: { v = rs.getString(columnName); break; } case TYPE_OBJECT: { v = rs.getObject(columnName); break; } } return v; } } public enum HSQLDBTypeEnum implements IType { TYPE_BOOLEAN(Boolean.class) , TYPE_BYTE(Byte.class) , TYPE_SHORT(Short.class) , TYPE_INTEGER(Integer.class) , TYPE_LONG(Long.class) , TYPE_DOUBLE(Double.class) , TYPE_BIGDECIMAL(BigDecimal.class) , TYPE_STRING(String.class) , TYPE_DATE(Date.class) , TYPE_TIME(Time.class) , TYPE_TIMESTAMP(Timestamp.class); public static HSQLDBTypeEnum get(Class<?> klass) { for (HSQLDBTypeEnum value : values()) { if (value.type != klass) continue; return value; } throw new IllegalArgumentException("unsupported class"); } private Class<?> type; private HSQLDBTypeEnum(Class<?> type) { this.type = type; } public Object getValue(ResultSet rs, String columnName) throws SQLException { Object v = null; switch (this) { case TYPE_BYTE: { v = rs.getByte(columnName); break; } case TYPE_SHORT: { v = rs.getShort(columnName); break; } case TYPE_INTEGER: { v = rs.getInt(columnName); break; } case TYPE_LONG: { v = rs.getLong(columnName); break; } case TYPE_DOUBLE: { v = rs.getDouble(columnName); break; } case TYPE_BIGDECIMAL: { v = rs.getBigDecimal(columnName); break; } case TYPE_STRING: { v = rs.getString(columnName); break; } case TYPE_DATE: { v = rs.getDate(columnName); break; } case TYPE_TIME: { v = rs.getTime(columnName); break; } case TYPE_TIMESTAMP: { v = rs.getTimestamp(columnName); break; } } return v; } }
使う側からは、
TypeFactory.HSQLDB.get(String.class).getValue(rs, "hoge");
などという形にできます。
いまはそのまま、ファクトリ内のenumを直接指定で書きましたが、これをアノテーションにもっていくなどすれば、さらに抽象度があがり、汎用化できます。