こんばんは。阪本です。
前回に引き続き、JDK7の新機能について見てみましょう。
5. Fork/Joinフレームワーク
データ処理をする場合など、大きな処理を、小さな複数の部分に分割して処理することがよくあります。
クイックソートやマージソート、高速フーリエ変換などがこれに該当します。
最近のコンピュータはマルチコア化が進んでいるので、
分割した処理をそれぞれ独立に複数スレッドで実行すると処理速度が向上します。
そのためのフレームワークがFork/Joinフレームワーク。
クイックソートだと、普通に書けば以下のようになります。
public class Sort<E extends Comparable<E>> { public void quickSort(List<E> list) { if (list.size() <= 1) { return; } // 軸要素を選択する E pivot = getPivot(list); // 大小2つのエリアに振り分け、境界のインデックスを取得する int partition = getPartition(list, pivot); // 左側のエリアを再帰的にソートする quickSort(list.subList(0, partition)); // 右側のエリアを再帰的にソートする quickSort(list.subList(partition + 1, list.size())); } }
これを、Fork/Joinフレームワークを用いて書くと、以下のようになります。
public class SortTask<E extends Comparable<E>> extends RecursiveAction { private List<E> list_; public SortTask(List<E> list) { this.list_ = list; } @Override public void compute() { if (this.list_.size() <= 1) { return; } // 軸要素を選択する E pivot = getPivot(this.list_); // 大小2つのエリアに振り分け、境界のインデックスを取得する int partition = getPartition(this.list_, pivot); // 左側のエリアを再帰的にソートするオブジェクトを生成する SortTask<E> leftTask = new SortTask<>(this.list_.subList(0, partition)); // 右側のエリアを再帰的にソートするオブジェクトを生成する SortTask<E> rightTask = new SortTask<>(this.list_.subList(partition + 1, this.list_.size())); // 右側のエリアを再帰的にソートする(別スレッド) rightTask.fork(); // 左側のエリアを再帰的にソートする leftTask.compute(); // 右側のエリアのソート完了を待つ rightTask.join(); } }
このクイックソートを実行するには、以下のように書きます。
List<Integer> list = new ArrayList<>(); ... ForkJoinPool pool = new ForkJoinPool(); pool.invoke(new SortTask<Integer>(list));
このように書くと、左右のエリアに分けたリストを再帰的にソートする部分が並列処理されるため、全体の処理速度が向上します。
もちろん、データ数が少ないとスレッド生成のオーバーヘッドが大きくなり、効果があまり出なくなります。
6. switch文におけるString使用
JDK6までは、switch文のcaseに指定できる型はint、short、byte、char、Enum型のいずれかでしたが、
JDK7からは、これにStringが加わりました。
つまり、
public String toJapanese(String english) { String japanese; switch (english) { case "black": japanese = "黒"; break; case "red": japanese = "赤"; break; default: japanese = "不明"; break; } return japanese; }
のようなことができます。
if文を用いて条件分岐するよりも、若干高速に動作します(コンパイラがhashCode()を使って最適化するため)。
また、String#equals()と同様、大文字小文字は区別されます。
個人的には、文字列でswitch文を使うくらいなら、せめてEnumにしようよ、とは思いますが。。
7. 例外再throw時の型推論
前回の投稿の「3.例外のマルチキャッチ」にも絡む話ですが、
例外をcatchして再throwするときの型チェックで、コンパイラが賢くなっています。
(メソッド先頭のthrows部分に注目。)
- JDK6
public void doSometing() throws Exception try { if ( ... ) { throw new FirstException(); } if ( ... ) { throw new SecondException(); } } catch (Exception ex) { // 例外処理 // 再throw throw ex; } }
- JDK7
public void doSometing() throws FirstException, SecondException try { if ( ... ) { throw new FirstException(); } if ( ... ) { throw new SecondException(); } } catch (Exception ex) { // 例外処理 // 再throw throw ex; } }
JDK7では、Javaコンパイラがcatchしうる例外が何かを理解するため、
throwsには、たとえExceptionでcatchした例外を再throwしても、catchしうる
doSomething()の呼び出し元では、Exceptionでのcatchが不要となります。
RuntimeExceptionも含めて、まとめて例外処理をして再throwする、というようなときに、
Exceptionを返さなくてよいので便利になっています。
ちなみに、以下のようにExceptionの中身を書き換えるとコンパイルエラーとなります。
コンパイラは賢い!
public void doSometing() throws FirstException, SecondException try { if ( ... ) { throw new FirstException(); } if ( ... ) { throw new SecondException(); } } catch (Exception ex) { // 例外処理 // 再throw ex = new Exception(); throw ex; // ←コンパイルエラー。以下のようなメッセージが出る。 // 「例外Exceptionは報告されません。スローするには、捕捉または宣言する必要があります」 } }
8. 数値の2進数表現
その2のラストは、数値の2進数表現について。
Javaで数値を書くときは、10進数、16進数、8進数での表現が可能でしたが、
JDK7からは2進数の表現も可能となりました。
数値の頭に「0b」をつければ、2進数と解釈されます。
int num1 = 0b0011010111110011; byte num2 = 0b01001110; long num3 = 0b11110011010010101010010101010101010000101110100101L;
ちなみに、「b」は大文字でも小文字でもOK。
今までと同様、数値を書いていた場所に、どこにでも2進数が書けます。
switch文とか、配列のインデックスとか。。
論理演算するときなどは便利ですね。
ここに書いた新機能以外にも、多くの機能追加や改善がされています。
新しいJava7に触れてみてください。