Java Generics
久々にJavaで開発中。5.0になって以降全然触れてなかったので、Genericsとかもちまちまと触れながらやってます。
で、このGenerics。C++のテンプレートみたいなのかと思ったら全然違うのね。今やりたいのはファイルから特定の構造を読み込むということで、そのためにFilterInputStreamを拡張してgetIntとかgetStringとかぐだぐだ作ろうかと思ったんだけど、Little endianとBig Endianの違いにも対処しなくちゃいけなくて、この方法で行くと同じエンディアン変換コードが至る所にコピーされて美しくないなぁと思って、そうだGenericsってこういうとき使えんじゃね、と思ったわけです。
具体的にはこんなことをしたい。
//欲しいコード in C++ //int i = getPrimitive<int>(fp, false); //short s = getPrimitive<short>(fp, true); //のように使う。 template<class T> T getPrimitive(FILE *fp, bool littleEndian) { unsigned char *buf = new unsigned char[sizeof(T)]; fread(buf, sizeof(unsigned char), sizeof(T), fp); T result = 0; for(int i = 0; i < sizeof(T); i++) { result |= littleEndian ? (buf[i]<<(8*i)) : (buf[i]<<(8*(sizeof(T)-i-1))); } delete[] buf; return result; }
しかし、Javaにはsizeofが無いのでこの手法が使えません。それならD言語みたいにtypeid(typeof(T))でif文に入れればいいかと思ったけど、typeofが無いので無理。妥協して引数にサイズを渡して読み込むのも考えたけど、返り値は固定になるので無理。サイズによってプリミティブのWrapperクラスを生成して返して、呼び出し元で(Integer)とかキャストするとUnboxingがあるのでvalueOfは書かなくても済むけど、中の分岐が・・・。
ここまで書いて気づいた。こうすればいいんじゃね?
//in Java //int i = (Integer)getPrimitive(in, 4, true); //short s = (Short)getPrimitive(in, 2, false); //こう使う。UnboxingによってWrapper->Primitiveは自動的に変換される。 Object getPrimitive(InputStream in, int size, boolean littleEndian) { byte[] buf = new byte[size]; in.read(buf, 0, size); long result = 0; for(int i = 0; i < size; i++) { result |= littleEndian ? (buf[i]<<(8*i)) : (buf[i]<<(8*(size-i-1))); } switch(size) { case 2: return new Short((short)result); case 4: return new Integer((int)result); case 8: return new Long(result); } throw new IllegalArgumentException("Invalid size."); }
あれ、これって符号どうなるんだっけ。時間ないので後で試す。
試した。キャストは単純に下位nバイトを取るだけみたいね。この実装の美しくないところは、型をキャストとsizeで2回表現してるところ。特にsizeはMagic Numberになるので見た目もよろしくなさげ。しかし1回の解は思いつかない。
考えられる妥協解は、文字列をテンプレートにしてunpackを実装することか?テンプレートで1回目、取り出すときのキャストで2回目の型表明があるけど、sizeを与えるよりは美しく見える。