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

JavaScriptはオブジェクト指向?

Twitterで@co_jit さんがツイートしていたので、ちょっと反応。

JavaScriptオブジェクト指向か?

個人的には、JavaScriptオブジェクト指向であるといえると考えています。

JavaScriptはクラスベースか?プロトタイプベースか?

が、もう1つ先にすすめると、そのオブジェクト指向がクラスベースで考えるのか、プロトタイプベースで考えるのか?という点がでてきます。

これは実現方法を考えるときに、大きな差がでてくる部分ですが、あまり意識されていない部分かな、とも。

Java,C#がクラスベースの代表格、JavaScriptがプロトタイプベースの代表格と考えています。

時間があるときにでも、両者の違いを書こうかな。

SQLiteを使う場合の注意点

さて、長いこと放置していたはてなダイアリーの方ですが、まとめ書きした方がいいものは、やっぱりこちらに書くということで。

AndroidSQLiteを使うケースは多々あると思いますが、明言されていない注意点があるので忘備録がてら。

SQLiteDatabase#closeは明示で呼ぶな、Cursor#closeは明示で呼べ

これはSQLiteの作りの話ですが、SQLiteではマルチスレッドに対してコネクションオープンからクローズまでは保障する、という作りになっています。
要はコネクション単位でスレッドセーフですよ、ということ。

AndroidSQLiteを使って検索系の処理をするのに、いわゆるWebアプリ的な作りで考えると、更新系処理ではCUD処理のあとにSQLiteDatabase#closeとしがちですが、android.database.sqlite.SQLiteException が発生するケースがあります。

どういったケースかというと、

  1. 検索系でSQLiteDatabase#queryでCursor取得
  2. 並行して更新系でSQLiteDatabase#close

というケース。

Cursorを取得しているときに、ちょうど更新系でcloseされていると、SQLITE_MISUSE (21) というエラーコード→SQLiteExceptionが返ります。
ぱっと見、NullPointerExceptionあたり?な感じもするかと思いますが、きちんとネイティブ層までコールされます。

なんでかというと、SQLiteDatabase#closeを呼ぶと、

  1. Java側のオブジェクト参照を解放
  2. JNI経由でネイティブコールし、dbclose処理
  3. SQLiteDatabase#mNativeHandleというメンバ変数をJNIコードで0初期化

します。文字通り、dbclose処理を行うわけです。

これにより、SQLiteDatabase#closeを呼んだオブジェクトはclose処理されます。んがしかし、これはあくまでメンバ変数です。
しかも、このオブジェクト自体は破棄されません。

これと並行してCursorを取っていると、問題が発生。Cursorもメンバ変数でSQLiteDatabaseを持ちます。
本来、SQLiteHelper#getReadableDatabase, getWritableDatabaseはSQLiteDatabase#isOpenがtrueだとなにもしませんが、falseだと新しく取得します。

つまり、ネイティブコールもするわけで。このとき、mNativeHandleはdbopen関数中で新しいポインタアドレスで更新されます。
こうなると、SQLiteDatabase#isOpenではtrueが返るのに、ポインタアドレスが異なる→SQLITE_MISUSE返る→SQLiteExceptionとなり、プロセスが終了しない限り例外が出続ける、という状況になります。
タイミングで発生するようになるわけですが、1度発生すると、ドハマリします。

解決はSQLiteDatabase#closeを呼ばないこと。SQLiteOpenHelper#closeも結局、SQLiteDatabase#closeにいくので同じです。
んじゃ、close処理はどーするの?って気になりますが、そこはSQLiteDatabase#finalizeがやってくれます。
つまるところ、最終的にはDalvikVMが破棄時にするので気にするな、ということになります。

ただし、Cursor#closeは呼んでおく必要があります。呼んでない場合、破棄時にERRORログがでます。
Read専用でCursorを使う限りはまだいいのですが、リソースを扱うのできちんと閉じましょう。

Dynamic Proxy その1

FLOSSSでみんなの期待を裏切ってDynamic Proxyをやると決めたので、なんとなく書いてみる。

んで、Dynamic ProxyはJavaの場合、1.3から導入されたので、
http://sdc.sun.co.jp/java/docs/j2se/1.3/ja/docs/ja/api/java/lang/reflect/Proxy.html
http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/java/lang/reflect/Proxy.html
Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle
Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle
んな感じなわけで。

ベースはここが参考になりますが、
Javaの理論と実践: 動的プロキシーで装飾する
これだけで足りないのも確かなわけで。

おいおい書いていく予定は未定ということで。

新年明けましておめでとうございます

昨年は納会後も仕事してたりして、なかなか収まったり収まらなかったりしたわけですが、昨日から仕事初めました。
地場は不況!の一言、といった状況ですが、あえて積極的な手も打っていきたいな、と思っています。

#やっぱり、目標でありつづけれるよう、行動していかないとね;-p

ちょっと一言

直接お会いしている方々は自分が元気なことはご存じかと思いますが、いろいろ書けないことだらけなだけで、私は元気です(?
で、ちょっと気になったことがあったので一言書いておこうかな、と。

世界同時不況にむけてまっしぐら、暗い話ばかりの世の中という世情ですが、それでも色々な動きはあるわけです。自分もコツコツと積み上げてきたお付き合いが色々な形で芽吹くようになってきました。そんな中で気になったのが、いわゆる大企業思考というか、『仕事を与えてやる』というスタンスで話をされる方がいまだにいることです。それも大企業に所属しているのではなく、そこの出入り業者に多い感じですね。
名目上、下請ではなくBPと呼ぼうが、中小零細は直接契約は結んでやらない、指定の大手を挟め、というパターンですね。んで、ウチが紹介してやるけど、おたく中小零細だから、ウチとちがって直はだめよ、って。なんでアンタがいうねん、それは元請が言うことやろ、というのがあるわけで。BPと呼ぼうが、単なる下請けという意識に変わりはない。また、大手を挟むのも、会社としての規則や理由があるのは当然分かるのですが、実際の技術蓄積をあまりにも無視しすぎていないか?という思いがあるわけで。

実際のところ、いわゆる下請の方が現場で泥のように働き、技術を蓄積しているケースが多いわけです。もう少しそういった部分が評価されるべきと思います。ただ、周りの同業を見る限り、あまりにもいままでと同じやり方を通しているという問題点も目に付きます。世情が変われば、当然アピールすべき相手も変わりますし、その中でどのようにして『次』のアピールをするか、という部分もあります。周りと同じことをして安心する、今までと同じことをして安心する、というのは世情が安定しているときならいいと思うのですが、昨今のような世情ではリスクを背負ったチャレンジも必要ではないかな?と。要は今までと違うことも手を付ける、ということです。ただし、本業を忘れず、『次』への準備という範疇で、と思いますが。
もちろん、大手であればあるほど研究開発にかけれるコスト・パワーが大きいのも事実ですが、中小零細がまったくコストをかけていないかといえば、それはNOです。中小零細の方が実際に担当する人が優秀なこともあり、結局同じようなところに落ち着くケースも多いのではないでしょうか。ま、中小零細が互角にいけるのはあくまで狭い範囲だけですけどね。広い範囲では太刀打ちできませんて。

#まー要は、中小零細だからって安くはないよ!って話にオチがつくのですが;-p