今更人に聞けないJava5からの新機能(Java SE 7)

More than 3 years have passed since last update.


はじめに

Javaのバージョン番号について、ところどころで1.8のように表記されている個所が出てきます。

リリース当初のJavaは1.3、1.4のようにマイナーバージョンがアップされていましたが、1.5のタイミングで5.0とメジャーバージョンを変えていくようになりました。

Java内のフォルダー名や表記など至るところで「1.X」と表記されている個所がありますが、実際にはXのバージョンを差すので混乱しないよう気をつけてください。


Java SE 7


当時の社会

Java SE 7は2011年7月28日にリリースされました。コードネームは「Dolphin」。

7月24日に、日本のテレビ放送において、東日本大震災で大きな被害を受けた岩手・福島・宮城の3県(被災3県)を除く44都道府県で、地上デジタルテレビ放送へ全面移行しました。

同日に日本のアナログBS放送も停波しました。

IT業界のニュースというと、2011年10月にアップルのスティーブ・ジョブズ氏が亡くなったことでしょうか。

朝出社してYahoo見た時には驚いた記憶があります。今僕たちが当たり前のように使っているいろいろなIT製品、ジョブズは『普通の人』の生活スタイルを変えてしまったわけで。

コンピュータは今や誰の手のひらの中にもあります。とんでもないものを遺していきました。

Java7は2006年に開発が始まり、延々と延期して、更なる延期を避けるため、大きな言語仕様の改訂などの部分は Java SE 8 に先送りしリリースされました。

ちなみにOracleによるSun買収後、初のメジャーリリースとなったJavaです。当時夜勤をしていたのですが、Sunのページのつもりで開いたらOracleのページが表示されて眠気が吹っ飛んだ記憶があります(笑)


NIO.2

Java1.4のときに追加された、新I/OのAPI群につけられたのが「New I/O」すなわちNIOですが、Java1.7でバージョンアップしました。

旧FileIOではjava.io.Fileクラスがファイルとディレクトリをあらわしていましたが、NIO2ではjava.nio.file.Pathクラスがファイルやディレクトリを表します。

また、NIO2ではjava.nio.file.Filesクラスとjava.nio.file.PathsクラスでPathクラスを扱った操作が定義されています。

Javaは後方互換性を持つため、従来のプログラムでも機能は実装できますが、NIO2を使用することで、これまで使いにくかったとされるファイル周りの機能が追加され、ありそうで無かったファイルを扱うメソッドも追加されました。

特にファイルのコピーは標準では存在しなかったため、昔は自分で実装を書いていた記憶がありますが、今はCopyメソッドを呼べば簡単に実現できます。

ひしだま's ホームページ:ファイル・ディレクトリ操作2

【ここが便利】

ディレクトリパスの取得、ファイルのコピーなどファイル周りの処理が容易になりました。

ファイルの権限やサイズ、更新時刻のsetter,getterも用意されているため実現できることが増えました。


Fork/Join フレームワーク

Java5でConcurrency Utilitiesが追加されたことは前の記事で書きましたが、J2SE 5.0がリリースされたのが2004年。

しかしその後CPUは大きく進化し、マルチコアが当たり前の時代になっています。

僕の安いノートPCですら2コア積んであります。企業が使用しているサーバはもっとでしょう。マルチコアに最適化された並列処理のフレームワークが求められました。

java.util.concurrent パッケージに追加された Fork/Join フレームワークは大量の計算を小さい部分に分けてマルチスレッドで計算する手法です。

Fork/Join Frameworkの性能について

本当に早くなるのか。こちらのレポートに書かれているソースコードをコピペして動かしてみました。


FibonattiTask1.java

//シングルスレッドでフィボナッチ数を計算

public class FibonacciTask1 {
int compute(int n){
return (n <= 1) ? n: compute(n-2)+compute(n-1);
}

public static void main(String[] args){
long start = System.nanoTime();
int fibonacci = new FibonacciTask1().compute(40);
long end = System.nanoTime();
System.out.println("F(40)="+fibonacci+"m elapsed="+(end-start));
}
}


F(40)=102334155m elapsed=583549710

こちらは直列処理でフィボナッチ数(40)を計算するまでにかかった時間です。


FibonattiTask2.java

import java.util.concurrent.*;

public class FibonacciTask2 extends RecursiveTask<Integer>{

int n;

FibonacciTask2(int n){
this.n = n;
}

@Override
protected Integer compute(){
return compute(n);
}

int compute(int n){
if(n <= 1){
return n;
}
ForkJoinTask<Integer> f1 = new FibonacciTask2(n-2).fork();
return compute(n-1) + f1.join();
}

public static void main(String[] args) {
int multiplicity = Integer.parseInt(args[0]);
long start = System.nanoTime();
ForkJoinPool pool = new ForkJoinPool(multiplicity); //多重度を指定
int fibonacci = pool.invoke(new FibonacciTask2(40));
long end = System.nanoTime();
System.out.println("F(40)="+fibonacci+" elapsed="+(end-start));
pool.shutdown();
}

}


このプログラムは引数で多重度(≒コア数)を設定し、Fork/Join Frameworkを用いてフィボナッチ数(40)を計算するプログラムです。

まず引数に1を与え、直列処理で使用して実行します。

F(40)=102334155 elapsed=5114100947

まったく同じ数字が出ます。

次に、引数=2、デュアルコアを想定して実行してみましょう。

F(40)=102334155 elapsed=2747713527

確かに早くなりました。

次に、引数=4、クアッドコアを想定して実行してみましょう。

F(40)=102334155 elapsed=2440176489

【ここが便利】

Fork/Joinフレームワークを実行すると、並列処理で高速化を図ることができます。

しかし、このフレームワークの効果を最大限に活かすにはテクニックが必要なようです。

Javaの並列処理に関してはオライリーをはじめ書籍が何冊も出ているので、そちらを読まなければ難しいのではないかと。

Javaの並列処理はJava8で更なる進化をします。そのため最新の書籍を読んだほうがたしかです。


try-with-resources

ファイルのオープンやDB接続、ネットワーク接続などの処理を行う際、処理が終った後ファイルやコネクションをクローズする必要があります。クローズしないとメモリリークします。

Java7以前では、クローズを実装するときはTry-Finallyを使用していました。


TryFinallySample.java

public class TryFinallySample {

public static void main(String[] args) throws Exception{
try{
System.out.println("リソースを使った処理を実行中");
}finally{
System.out.println("リソースclose");
}
}
}


リソースを使った処理を実行中

リソースclose

Java7からはtry-with-resources構文が利用出来るようになりました。この構文を使用することで、リソースがクローズされることが保障されます。

try-with-resource構文を使うことができるのはjava.lang.AutoCloseableインタフェースを実装するしているクラスです。

Javaの標準ライブラリでリソースとして使われるクラスの多くはjava.io.Closeableインタフェースを実装していますが、このインタフェースはjava.lang.AutoCloseableを継承しているので、リソースのクローズを書かなくても大丈夫となりました。

AutoCloseableを実装した内部クラスCloseableSampleのオブジェクトを作成し、クローズ時に処理が走ることを確認しました。


TryWithResourceSample.java

public class TryWithResourceSample{

public static void main(String args[]) throws Exception{
try(CloseableSample sample = new CloseableSample()){
System.out.println("リソースを使った処理を実行中");
throw new RuntimeException();
}
}

private static class CloseableSample implements AutoCloseable{
public void close() throws Exception{
System.out.println("リソースをclose");
}
}
}


リソースを使った処理を実行中

リソースclose

実行すると以下のようになります。close処理が自動で走ったことが分かります。

【ここが便利】

クローズ処理をし忘れてメモリリーク、などという状況を防ぐことが出来ます。


ダイヤモンド記法

Java5で総称型(ジェネリクス)を紹介しました。リストにおいてキャスト不要、型の制限ができる便利な機能です。

List<String> l = new ArrayList<String>();

これを

List l = new ArrayList<>();

上記のように書くことができるようになりました。

<>の形がダイヤモンドに似ていることからこれをダイアモンド演算子と呼びます。

ジェネリクスを省略して記述した場合はJavaのコンパイラが型を推論して適切な型を補完します。

【ここが便利】

コーディング量を減らすことが出来ます。


例外のマルチキャッチ

これは分かりやすい話で、複数の例外をまとめてキャッチできるようになりました。

try {

...
} catch(IOException e) {
//IOException時の処理
} catch(ClassNotFoundException e) {
//ClassNotFoundException時の処理
}

try {

...
} catch(IOException | ClassNotFoundException e) {
//IOException or ClassNotFoundException時の処理
}

と書くことが出来ます。

マルチキャッチ内で例外の種別を判定したい場合は以下のプログラムのように、catch文内で分岐判定を使います。


MultiExceptionTest.java

public class MultiExceptionTest extends Exception{

public static void main(String[] args) {
int result;
try {
result = Integer.parseInt(args[1]) / Integer.parseInt(args[0]);
System.out.println(result);
} catch(ArrayIndexOutOfBoundsException | ArithmeticException e) {
if(e instanceof ArrayIndexOutOfBoundsException) {
System.out.println("ArrayIndexOutOfBoundsException");
}
if(e instanceof ArithmeticException) {
System.out.println("ArithmeticException");
}
}
}
}


注意点として、複数指定された例外のうち、一方の例外クラスが他の例外クラスのサブクラスの場合、コンパイルエラーが発生します。

【ここが便利】

冗長な例外処理を書かなくてもよくなりました。


switchで文字列が使用可能に

マルチスレッドもリソースのオープンクローズも使わないよ・・・という貴方にもswitchで文字列を判定条件で使用可能になったことは朗報でしょう。


SwitchSample.java

public class SwitchSample {

public static void main(String[] args){
String test = "test";
switch (test) {
case "a": case "b":
System.out.println("a or b");
break;
case "test":
System.out.println("test");
default:
System.out.println("finish");
break;
}
}
}


【ここが便利】

文字列で受け取った値でcase文を使いたい時にいちいち変換する必要はなくなりました。

ただし比較する文字列変数がNULLの場合はNullPointerExceptionが発生するので注意してください。(Stringクラスのequals()メソッドによる判定のため)


Java7の新機能はJava5に比べると少ないものですが、その分Java8は多くの新機能が追加されました。