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を直接指定で書きましたが、これをアノテーションにもっていくなどすれば、さらに抽象度があがり、汎用化できます。