Interceptorが織り込まれたObjectはシリアライズ/デシリアライズ可能か

経緯

Javaの場合、基本的に以下のものがシリアライズ/デシリアライズ可能である。

しかし、Interceptorが織り込まれたインスタンスは純粋なクラス型に属さない。(織り込まれる前の基本クラスにInterceptorがマージされた、特別な型のインスタンスである)
このため、たとえSerializableインタフェースを実装していたとしても、Interceptorが織り込まれたインスタンスシリアライズ/デシリアライズできないのではないかと考え、調べてみた。(但し、今回確認したのはシリアライズのみ)

環境

クライアント環境で簡単にInterceptorの織り込みができるGoogle Guiceを使用した。

Interceptorを織り込まないインスタンスで確認

比較のため、まずはInterceptorを織り込まないインスタンスで確認する。

確認ソース

以下のコードはServiceクラスのインスタンスを生成し、クラス名の出力とシリアライズを行う。(クラス名を出力しているのは、Interceptorを織り込む/織り込まないで、クラスの型が変わることを示すため)

package com.bawsin.sample.main;
...
public class Main {
	
	public static void main(String[] args) throws Exception {
		Service service = new Service();
		System.out.println(service.getClass().getName());
		writeObject(service, "service.ser");
	}
	
	public static void writeObject(Object obj, String destFileName) throws IOException {
		FileOutputStream fileOutputStream = null;
		ObjectOutputStream objectOutputStream = null;
		
		fileOutputStream = new FileOutputStream(destFileName);
		objectOutputStream = new ObjectOutputStream(fileOutputStream);

		objectOutputStream.writeObject(obj);

		fileOutputStream.close();
		objectOutputStream.close();
	}
	
	static class Service implements Serializable {
		
		private static final long serialVersionUID = -8686921949515900053L;

		public String doService(String name) {
			return "Hello, " + name;
		}
	}
}
実行結果

インスタンスからgetClass().getName()すると、正しく"Service"クラスが出力される。また、例外は発生せず、"service.ser"ファイルが作成される(ファイルについては省略)

com.bawsin.sample.main.Main$Service

Interceptorを織り込まないインスタンスで確認(その2)

次に、GuiceをFactoryとし、ここからServiceインスタンスを取得した場合について確認する。

確認ソース

前述したコードほぼ同じである。唯一の違いは"Service"インスタンスをnewするのではなく、Guiceから取得する点である。

package com.bawsin.sample.main;
...
public class Main {
	
	public static void main(String[] args) throws Exception {
		Injector injector = Guice.createInjector();
		Service service = injector.getInstance(Service.class);
		System.out.println(service.getClass().getName());
		writeObject(service, "service.ser");
	}
	
	public static void writeObject(Object obj, String destFileName) throws IOException {
		FileOutputStream fileOutputStream = null;
		ObjectOutputStream objectOutputStream = null;
		
		fileOutputStream = new FileOutputStream(destFileName);
		objectOutputStream = new ObjectOutputStream(fileOutputStream);

		objectOutputStream.writeObject(obj);

		fileOutputStream.close();
		objectOutputStream.close();
	}
	
	static class Service implements Serializable {
		
		private static final long serialVersionUID = -8686921949515900053L;

		public String doService(String name) {
			return "Hello, " + name;
		}
	}
}
実行結果

特にInterceptorの織り込み設定は定義していないため、Serviceインスタンスが取得され、結果は同じとなる。

com.bawsin.sample.main.Main$Service

Interceptorを織り込んで確認

いよいよ本番。Interceptorを織り込んだServiceインスタンスで確認する。

確認ソース

Interceptorを織り込むように定義したModuleをGuiceのInjectorに設定する。

package com.bawsin.sample.main;
...
public class Main {
	
	public static void main(String[] args) throws Exception {
		Injector injector = Guice.createInjector(new AbstractModule() {
			@Override
			protected void configure() {
				bindInterceptor(Matchers.any(), 
						        Matchers.any(), 
						        new SampleInterceptor());
			}
		});
		
		Service service = injector.getInstance(Service.class);
		System.out.println(service.getClass().getName());
		writeObject(service, "service.ser");
	}
	
	public static void writeObject(Object obj, String destFileName) throws IOException {
		FileOutputStream fileOutputStream = null;
		ObjectOutputStream objectOutputStream = null;
		
		fileOutputStream = new FileOutputStream(destFileName);
		objectOutputStream = new ObjectOutputStream(fileOutputStream);

		objectOutputStream.writeObject(obj);

		fileOutputStream.close();
		objectOutputStream.close();
	}
	
	static class Service implements Serializable {
		
		private static final long serialVersionUID = -8686921949515900053L;

		public String doService(String name) {
			return "Hello, " + name;
		}
	}
	
	static class SampleInterceptor implements MethodInterceptor {

		private static final long serialVersionUID = 4237617691983738210L;

		public Object invoke(MethodInvocation invocation) throws Throwable {
			Object ret = invocation.proceed();
			return ret;
		}
	}
}
実行結果

Guiceから取得したインスタンスはServiceクラス型ではなく、またシリアライズ時に例外が発生した。

com.bawsin.sample.main.Main$Service$$EnhancerByGuice$$3a81e87c

Exception in thread "main" java.io.NotSerializableException: com.google.inject.InterceptorStackCallback
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at com.bawsin.sample.main.Main.writeObject(Main.java:44)
	at com.bawsin.sample.main.Main.main(Main.java:34)

例外メッセージから、"com.google.inject.InterceptorStackCallback"が原因でシリアライズ時に例外が発生したことがわかる。該当のクラスはGuiceのクラスであるため、ソースを確認したところ、確かにSerializableインタフェースを実装していなかった。
別の観点として作成したInterceptorはSerializableインタフェースを実装していなかったため、実装するように変更して試してみたが、結果は同じであった。

以上の結果から、Guice使用の場合、Interceptorを織り込んだObjectのシリアライズ時に、GuiceのInterceptorStackCallbackをシリアライズしようとして例外が発生するようだ。このため、Interceptorを織り込んだインスタンスシリアライズできない。
現状では、どうすれば上記例外を回避できるか不明。

怒首領蜂大復活ver1.5リリース

怒首領蜂大復活ver1.0のリリースから約1ヶ月のロケテストを経てようやくver1.5がリリースされた模様。

ver1.0から大きく変わったと思った部分は以下の点。

  1. 自機の追加
    • ショットが曲がるタイプの自機が追加された。
  2. スコアの調整
    • 5面以外の点効率を上方修正(5面はたぶん下方修正)
    • 1000億以上の桁に対応
  3. POWERスタイルの強化
    • 中ボス/ボスの弾を消せるようになった(但し使用回数依存)
    • ハイパーゲージの上昇率が大幅に向上
  4. BOMBスタイルの弱体化?
    • ボムアイテムを取得した際に、ストック数が+1されるだけになった

スコアについては良調整だと思う。これにより、カンストの心配がなくなった。また5面に点効率が集中していた問題点も修正されている。(主に中型機や大型機の破壊点が大幅UPしているので注目。)

POWERスタイルについてはBOOST時に弾を消すとハイパーゲージが上昇するようになったため、大幅にハイパーゲージが上昇するようになった。ハイパー発動→ガリガリ弾消し→ハイパーゲージMAX→ハイパー発動 のループで各ステージ中の8〜9割の時間ハイパー状態でいることも可能。但し、中ボス/ボス時はハイパーを使いすぎると、ほとんど弾を消せないので注意。

敵配置や耐久力に特に変化はないため、パターンについては1.0のものをそのまま流用できるはず。

とりあえず、良い方向に修正されているようなのでよかった。しかし、今回はあからさまなバグレベルの仕様が多かったなあ。(ボム取得時MAXまで回復とかスコアカンストとか)この辺、ver1.0時の調整でなんとかならなかったのかなあ。

以下の本を読んでいて、オブジェクトとクラスの関係についてなるほどと思った記述があった。*1

Rubyソースコード完全解説

Rubyソースコード完全解説


第1章 Ruby言語ミニマムの 「クラスとメソッド」の項

本来オブジェクト指向システムにおいてメソッドとはオブジェクトに所属するものだ。だがそれはあくまで理想の世界でのこと。普通のプログラムなら同じメソッドの集合を持つオブジェクトがたくさんあるわけで、もしバカ正直にオブジェクト単位で呼び出せるメソッドを記憶していたりしたら大変なことになってしまう。そこで普通はクラスとかマルチメソッドのような仕組みを利用して定義の重複をなくすわけだ。


なるほど。オブジェクト指向の場合、メモリ上に多くのオブジェクトが存在していて、それらのオブジェクトが互いにメッセージのやり取り(オブジェクトに所属しているメソッド=メンバ関数の呼び出し)を行うことでプログラムが動作する。しかし、あるオブジェクトに所属するメソッドを実行しようとした場合、そのメソッドがオブジェクトに存在しない場合は実行することができない。このため、オブジェクトが持っているメソッドを何らかの方法で管理する必要があるが、引用の記述にあるようにメモリ上に存在している全てのオブジェクトに対して持っているメソッドを管理していたら大変だろう。


そこで、クラスの仕組みがあるわけだ。あるオブジェクトに対してメソッド実行を要求した場合、まずそのオブジェクトが「所属」しているクラスを調べ、次に要求したメソッドがクラスに定義されているかどうかを調べるわけだ。こうすれば、メソッドを管理する規模は存在しているクラスだけに限定されるわけだ。全く同じメソッドを持っている複数のオブジェクトを別々に管理する必要がなくなる。*2


メジャーなオブジェクト指向言語は「クラス」の仕組みを採用している。「マルチメソッド」については、調べた範囲ではLisp系の言語で実装されている機能のようだ。ただし、Lisp系の言語はオブジェクト指向言語というよりは、オブジェクト指向言語も実現できると言った方が正しいのかな。
オブジェクト指向って何?という疑問について説明できるようになる日はまだまだ遠そうだ...

*1:リンク先の書籍は現在希少であり、在庫切れになっています。但し、著者が本と同じ内容をWeb上に公開されています。URLはhttp://www.loveruby.net/ja/rhg/book/

*2:ここまでは「クラス」についての記述ですが、引用にある「マルチメソッド」については勉強不足のため、記述していません。無念。

休暇を利用して読んだ本。

大聖堂 (上) (ソフトバンク文庫)

大聖堂 (上) (ソフトバンク文庫)


大聖堂 (中) (ソフトバンク文庫)

大聖堂 (中) (ソフトバンク文庫)


大聖堂 (下) (ソフトバンク文庫)

大聖堂 (下) (ソフトバンク文庫)

2日かけて読了。上, 中, 下の3部構成で1冊約600ページあり、合計約1800ページになりますが、その世界観にどっぷりハマってしまい、寝食を忘れて読んでいました。舞台は12世紀のイングランド。国王, 領主, 騎士, 司祭, 牧師, 商人, 建築家, 農民など、様々な立場の人々が登場しますが、共通して彼らが行動するトリガとなるのは各々の欲望(支配欲だったり、出世欲だったり、名誉欲だったり、金銭欲だったり、色欲だったり)です。欲望を満たすために複雑に利害関係が成立してゆき、物語は展開します。個人的にはそれらの欲望の絡み合いがすごい面白くて引き込まれました。ぜひ読んで欲しい本。


悪童日記 (ハヤカワepi文庫)

悪童日記 (ハヤカワepi文庫)


ふたりの証拠 (ハヤカワepi文庫)

ふたりの証拠 (ハヤカワepi文庫)


第三の嘘 (ハヤカワepi文庫)

第三の嘘 (ハヤカワepi文庫)

タイトルこそ違いますが、これらを合わせて3部構成になっています。オーストリアとハンガリーの国境付近の小さな町が舞台であり、第二次世界大戦の終戦時の情景が描かれています。まず「悪童日記」の内容に度肝を抜かれ、「ふたりの証拠」で意表を突かれ、「第三の嘘」でひっくり返されます。これもすごい面白いのでぜひ読んで欲しい本ですね。

caveの新作STG「デススマイルズ」

デススマイルズが稼動しました。
以下感想を箇条書きでつらづらと。

  • ちっともゴシックホラーじゃない
  • 赤い髪のキャラの声が違和感があって慣れません
  • 敵キャラはすげー良い(悪魔城ドラキュラみたい)
  • なぜボスが牛なの?(湖畔ステージとか)


閑話休題。
スコア稼ぎの流れは以下のようになります。

  • ザコ破壊してアイテム回収
  • アイテムのカウンタが1000行ったら準備完了
  • ザコが大量に出るところまで進む
  • A+B同時押しでパワーアップし、がりがりザコを破壊
  • ザコから大量の得点アイテムを回収(アイテムカウンタは減っていく)
  • アイテムカウンタがゼロになって終了(最初に戻る)

とまあ、すげー単純なのです。正直、すぐにパターンが煮詰まってしまって、正直短期間でやらなくなるのではないかと思っていました。
しかし、まだもう一つだけ重要な稼ぐ要素がありました。このゲームは各ステージごとに難易度(LV1〜LV3)が選べるのですが、4つ目か5つ目のステージまで全てLV3を選択していると、それ以降のステージはザコ破壊時に十字架状の撃ち返し弾が発生するようになります。
撃ち返し弾は使い魔で吸収することが出来、弾一つごとにアイテムカウンタが1上がります。撃ち返し弾の量は多いため、難易度は跳ね上がりますが、同時に上記に示したスコア稼ぎのサイクルが周りやすくなります。このため、スコア稼ぎにはLV3選択が必須となります。
何気に長いこと遊べるゲームかもしれません。

SQL関連

本屋をぶらついていたときにタイトル見てそのまま買ってしまった本。

SQL Hacks ―データベースを自由自在に操るテクニック

SQL Hacks ―データベースを自由自在に操るテクニック

最近ものすごいSQLに興味があるので、これで勉強かな。
使用するDBがMySQLならば、以下の本もお勧め。

MySQL全機能リファレンス

MySQL全機能リファレンス

Amazonの書評を見るとかなり評価高いし、実際に見てみたけどかなり良い本だと思います。
ただし、バージョン4.x対応なので、5.xには合わない内容があるかも。

黒体と量子猫

知り合いに紹介された本。

黒体と量子猫〈1〉ワンダフルな物理史 古典篇 (ハヤカワ文庫NF―数理を愉しむシリーズ)

黒体と量子猫〈1〉ワンダフルな物理史 古典篇 (ハヤカワ文庫NF―数理を愉しむシリーズ)


黒体と量子猫〈2〉ワンダフルな物理史 現代篇 (ハヤカワ文庫NF―数理を愉しむシリーズ)

黒体と量子猫〈2〉ワンダフルな物理史 現代篇 (ハヤカワ文庫NF―数理を愉しむシリーズ)

なんでも文系の人間にも読めるように配慮されている本だとか。帯とか目次を見る限り、物理史について書いてあるようだ。
物理や数学は学生の時以来全く触れておらず、数式とかを見るだけで拒否反応を起こしてしまうので、このような本はありがたい。じっくり読んでみよう。