List型からのtoArray
以下のようにjava.util.List#toArray()では、引数に配列型のインスタンスを渡して配列の型を確定します。
List<SampleBean> list = new ArrayList<SampleBean>(); SampleBean[] res = list.toArray(new SampleBean[0]);
引数で空インスタンスを渡さないと、Object[]として返すのでClassCastExceptionが発生します。
List型でジェネリクスを指定している場合、(オブジェクトを触る側からすると)既に配列にすべき型は分かっているので、以下のように書けてもいいのではないかと思ったりするわけですが、
SampleBean[] res = list.toArray();
ArrayListの実装などを読んでみたところ、現状ではちょっと難しそうです。後方互換性の問題ではなく、Javaのジェネリクスの仕様的に不可能にみえます。引数で型を受け取っていなければ、型を確定させる手段がありません。
ArrayListの場合、内部的に持っているelementData(配列)の型を取ってくれば引数ありtoArrayがやっている事を内部的に完結できそうですが、これだと要素が全てnullの場合(例えばnewしただけのList)に型を確定できません。
私は、以下のようなArrayUtil#toArray()を作って使ったりしますが、このutilはやはり上記の問題を解決できていなくて、size()が0の場合はnullで返しています。
主な用途がReflection APIで配列にして引数に渡すケースなので、問題はないのですが。
public static <T> T[] toArray(List<T> arg) { if (arg == null) return null; int len = arg.size(); if (len <= 0) return null; Class<?> clazz = arg.get(0).getClass(); T[] retArr = (T[]) Array.newInstance(clazz, len); for (int i = 0; i < len; i++) retArr[i] = arg.get(i); return retArr; }
リリース前に単体テスト全実行
PerlのCPANモジュールはインストール時にmake testで単体テストを全実行しますが、Javaで単体テストを全実行したいという場合、antのタスクを作って組み込みます。
説明を付したタスクのサンプルが以下のような感じになります。
<target name="maketest"> <!-- 準備 --> <delete dir="test" failonerror="false" /> <mkdir dir="test/classes" /> <mkdir dir="test/result" /> <!-- テストクラスのコンパイル --> <javac srcdir="src/test/java" destdir="test/classes" target="1.5" encoding="UTF-8" deprecation="on" optimize="off" debug="on"> <!-- クラスパス設定 --> <classpath> <!-- テスト対象のclassを含める --> <pathelement path="build/classes" /> <!-- コンパイルに必要なjarを含める --> <fileset dir="lib"> <include name="**/*.jar" /> </fileset> </classpath> </javac> <!-- テストに必要なリソースをコピー --> <copy todir="/src/test/resources"> <fileset dir="test/classes"> <include name="**/*" /> </fileset> </copy> <!-- テスト全実行 --> <junit> <!-- テスト結果出力フォーマット --> <formatter type="xml"/> <!-- クラスパス設定 --> <classpath> <!-- テスト対象 --> <pathelement path="build/classes" /> <!-- テストクラス --> <pathelement path="test/classes" /> <!-- 必要なjarファイル --> <fileset dir="lib"> <include name="**/*.jar" /> </fileset> </classpath> <!-- 指定したテストをバッチで全実行 --> <!-- ※テストが通らないとリリースできないように設定(halton〜)--> <batchtest todir="test/result" fork="yes" haltonerror="true" haltonfailure="true"> <fileset dir="${src_test_java_dir}"> <include name="**/*Test.java"/> </fileset> </batchtest> </junit> <!-- テスト結果をHTMLのレポートに変換 --> <junitreport> <fileset dir="${test_result_dir}"> <include name="TEST-*.xml"/> </fileset> <report format="frames" todir="test/result"/> </junitreport> </target>
出力したテスト結果の例は以下のような感じになります。
http://seratch.net/works/java/unittest/java-simple-fh/0.0.1/
私は上記くらいで十分かなという感じですが、「djUnitのカバレッジで出したい!」という場合は、以下のページの説明のようにすると出来るようです。
http://works.dgic.co.jp/djwiki/Viewpage.do?pid=@616E74E381A7646A556E6974E38292E4BDBFE38186
追記:(11/03)
つ、mavenって事ですか。
そうじゃない環境の場合、上記のようにantでやるという用途はありそうですが。
抽象メソッドの引数名
例えばライブラリをつくってjarにして配布した場合、Eclipse等のIDE上でinterface型オブジェクトのメソッド補完するときに引数の変数名がjava.lang.Stringなら「s」とかjava.lang.Objectなら「obj」とかになってしまうのをどうににかできないかなと少し調べたのですが。
通常、native または abstract メソッドではローカル変数情報は使用できない (つまり、引数名情報は使用できない)
上記のJavadocにさらっと触れている通り、Javaの抽象メソッドの引数情報はclassファイルになった段階では、型のみになっているようです。
確かにバイナリエディタでそれとなくclassファイルを見てみても、classの場合は引数の変数名も見えてるけど、interfaceやabstract classの抽象メソッドの場合、型しか持ってないようにみえます。
Javaとしては「どうしても引数に意味を持たせたいケースであれば、そうと分かる型を用意するべき」という思想なのかなという気はします。
ただ、使う側からすると、Stringでいいところをわざわざ独自クラスでnewしたり、キャストしないといけなくなったりするのが果たして便利なのかとか考えると悩ましい感じです。
「あんまり横着せず少しはJavadoc嫁」でもいいんでしょうけど。
RomeでRSSフィードをパース
初めて使ったのですが、Romeはフォーマットを意識する必要もなく簡単にRSSフィードのパースができて素晴らしいですね。
import java.net.URL; import java.util.List; import org.xml.sax.InputSource; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndFeed; import com.sun.syndication.io.SyndFeedInput; import com.sun.syndication.io.XmlReader; public class RomeSample { public static void main(String[] args) throws Exception { SyndFeedInput feedInput = new SyndFeedInput(); URL url = new URL("http://d.hatena.ne.jp/srkzhr/rss"); SyndFeed feed = feedInput.build(new XmlReader(url)); List<SyndEntry> entries = feed.getEntries(); for (SyndEntry entry : entries) { System.out.println(entry.getTitle()); System.out.println(entry.getLink()); } feed = feedInput.build(new InputSource(url.openStream())); entries = feed.getEntries(); for (SyndEntry entry : entries) { System.out.println(entry.getTitle()); System.out.println(entry.getLink()); } } }
ただ、同じものをGoogle App Engine上で動作させてみたところ、うまくいきませんでした。
ローカル環境では以下のライブラリのみで動作しますが
- rome-1.0.jar
- jdom.jar (1.1)
以下リンクにある通り、クラウド上ではxercesも合わせてデプロイしないと動作しないので注意が必要です。
当方ではjdomに同梱されているxerces.jarを持っていくと正常に動作する事を確認できました。
http://groups.google.co.jp/group/google-app-engine-japan/browse_thread/thread/e9283474dda71bb3?pli=1
http://code.google.com/p/googleappengine/issues/detail?id=1367
読み込んだリソースを必要十分な配列長にする
リソース上のファイルからbyte配列でデータを取得する場合にデータを必要十分な配列長に格納する実装を書いてみました。
private byte[] _getFileContentInByteArray(InputStream is, byte[] registeredArr, int bufArrLen, int depth) throws IOException { int registeredArrLen = (registeredArr == null) ? 0 : registeredArr.length; // buffering array to read byte[] bufArr = new byte[bufArrLen]; is.read(bufArr); Integer endIdxOfArr = null; // check end index of array for (int i = 0; i < bufArrLen; i++) { if (endIdxOfArr != null) { // continued byte 0 if (bufArr[i] != 0) endIdxOfArr = null; } else { // first byte 0 if (bufArr[i] == 0) endIdxOfArr = i; } } // might be binary file if (endIdxOfArr != null && endIdxOfArr > bufArrLen - 16) endIdxOfArr = null; byte[] resultArr = null; // merge registered array and buffering array if (endIdxOfArr == null) { // not end index of array if (registeredArrLen == 0) { // first buffering resultArr = new byte[bufArrLen]; for (int i = 0; i < bufArrLen; i++) resultArr[i] = bufArr[i]; } else { resultArr = new byte[registeredArrLen + bufArrLen]; System.arraycopy(registeredArr, 0, resultArr, 0, registeredArrLen); System.arraycopy(bufArr, 0, resultArr, registeredArrLen, bufArrLen); registeredArr = null; bufArr = null; } // recursive execute depth = depth + 1; return _getFileContentInByteArray(is, resultArr, bufArrLen * depth, depth); } else { // end index of array if (registeredArrLen == 0) { resultArr = new byte[endIdxOfArr]; for (int i = 0; i < endIdxOfArr; i++) { resultArr[i] = bufArr[i]; } } else { resultArr = new byte[registeredArrLen + endIdxOfArr]; System.arraycopy(registeredArr, 0, resultArr, 0, registeredArrLen); System.arraycopy(bufArr, 0, resultArr, registeredArrLen, endIdxOfArr); } } return resultArr; }
もしかして、車輪の再発明・・なのかな。
追記:(10/4)
画像などのバイナリファイルの場合、途中に0のbyteが入ってくるので、判定を修正しました。
バイナリファイルの場合、テキストでいうEOF(0x1A)も途中で普通に出てくるので、ある程度余裕をもって見てみて「もうずっと0が続いてるからファイルは終わってる」というジャッジをするしかないと思っているのですが、正しいんでしょうか。
ただ、バッファ配列の切れ目の末尾でずっと0が続いた場合、ファイルが終わったのかどうか不明なので、とりあえず16個以下の0の連続だったら次もみるようにしています。
どれくらいが妥当なのか把握していません。16というのはいくつかテストした結果で10連続を超える箇所が見つからなかったので、切りのいいところでそうしているだけという感じです。
ArrayListの全要素取得パフォーマンス比較
java.util.List型(ArrayListでインスタンス化)コレクションを全要素取得するような処理の以下の3つについてパフォーマンスを比較してみました。
- get(index)でアクセス
- Iteratorを取得して使用
- 拡張for文
なお、比較には拙作のツールを使用しました。
http://sourceforge.jp/projects/java-cpt/wiki/FrontPage
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import jp.sourceforge.javacpt.ComparativePerformanceTest; import jp.sourceforge.javacpt.ComparativePerformanceTestHelper; public class LoopSample { public static void main(String[] args) throws Exception int executeTimes = 100000; LoopSample instance = new LoopSample(); ComparativePerformanceTest test = ComparativePerformanceTestHelper.initialize( executeTimes, instance); // 対象のListオブジェクト List<Object> target = new ArrayList<Object>(); for (int i = 0; i < 100; i++) target.add(new Object()); // get(index)でアクセス String indexLoop = "indexLoop"; long indexLoopResult = 0; // Iteratorを取得して使用1 String iterLoop1 = "iterLoop1"; long iterLoop1Result = 0; // Iteratorを取得して使用2 String iterLoop2 = "iterLoop2"; long iterLoop2Result = 0; // 拡張for文 String enhancedForLoop = "enhancedForLoop"; long enhancedForLoopResult = 0; // 10回実行 for (int i = 0; i < 10; i++) { indexLoopResult += ComparativePerformanceTestHelper.invoke(test, indexLoop, indexLoop, List.class, target); iterLoop1Result += ComparativePerformanceTestHelper.invoke(test, iterLoop1, iterLoop1, List.class, target); iterLoop2Result += ComparativePerformanceTestHelper.invoke(test, iterLoop2, iterLoop2, List.class, target); enhancedForLoopResult += ComparativePerformanceTestHelper.invoke(test, enhancedForLoop, enhancedForLoop, List.class, target); } // それぞれ平均値を出力 System.out.println(indexLoop + " : " + indexLoopResult / 10); System.out.println(iterLoop1 + " : " + iterLoop1Result / 10); System.out.println(iterLoop2 + " : " + iterLoop2Result / 10); System.out.println(enhancedForLoop + " : " + enhancedForLoopResult / 10); } public void indexLoop(List<Object> target) { int len = target.size(); for (int i = 0; i < len; i++) { Object each = target.get(i); } } public void iterLoop1(List<Object> target) { for (Iterator<Object> iter = target.iterator(); iter.hasNext();) { Object each = iter.next(); } } public void iterLoop2(List<Object> target) { Iterator<Object> iter = target.iterator(); while (iter.hasNext()) { Object each = iter.next(); } } public void enhancedForLoop(List<Object> target) { for (Object each : target) { } } }
結果としては以下の通りです。
indexLoop : 342 iterLoop1 : 839 iterLoop2 : 845 enhancedForLoop : 842 indexLoop : 328 iterLoop1 : 726 iterLoop2 : 712 enhancedForLoop : 712 indexLoop : 304 iterLoop1 : 720 iterLoop2 : 707 enhancedForLoop : 731
要素の取得部分に限ってみると、indexを指定してアクセスする方が2倍以上高速であることが分かります。
ループの中の処理内容によっては誤差の範囲内といえますが、共通ライブラリや頻繁に呼び出される処理を実装するときは、少し意識しておいた方がいいのかもしれません。
追記:(8/23)
内容の割にサンプルがどうも冗長だったので、ヘルパークラスをつくってよりシンプルにしました。
Arrays.asListの使いどころ
Arrays.asList()は、配列*1を引数にとって固定長のjava.util.List型のオブジェクトを生成するメソッドです。
このstaticメソッドが返すオブジェクトはjava.util.Arrays$ArrayListクラスでインスタンス化され、内部的にはListの型パラメータで指定した型(なければjava.io.Serializable型)の配列として保持されています。
Arrays.asListで生成したListオブジェクトに対して、そうとは知らずに後続の処理でaddしようとするとjava.lang.UnsupportedOperationExceptionが発生します。removeでも同様です。要素の書き換えはできるけど、要素数の変更はできないというわけですね。
型だけだとこの危険性を検知できないので、使いどころは固定長である事が自明なところ*2か、明らかにそうと分かるようなフィールド名を付けた場合ということになるでしょうか。