Java8に注目してみました

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る

ADMAGEユニットの小林です。

年も明けたので、今年3月のリリースが目前のJava8について書いてみようと思います。
去年はbeta版で色々試してみましたが、その中で気になったところをピックアップしてみます。
サンプルを含みますので、長い記事となりますがご了承ください。

Java8に注目してみました

Java8の新機能

注目した新機能は
(1)Lambda式の導入
(2)Stream API
(3)Date and Time API
の3点です。

(1)Lambda(ラムダ)式

Lambda式はLisp、C#などで導入されている機能で、
「ラムダ計算(計算の実行を関数への引数の評価(eval)と適用(apply)としてモデル化・抽象化した計算体系)を用いた記述法」です。
Java8で強化された型推論とLambda式を用いて、匿名メソッドの記述を簡素化できるようになります。

「Listの全要素を [ ] 囲いに置換する」場合を例にしてみます。
以下のサンプルはすべて同じ結果を返します。

結果: list:[[a], [b], [c]]

1. 匿名クラスを使わない
一番基本的な記述方法です。
List.replaceAll()に目的の処理が書かれているメソッドを渡しています。

// 文字列置換用のクラス
class Operation implements UnaryOperator{
	@Override
	public String apply(String t){
		// 引数を[]で囲いreturn
		return "[" + t + "]";
	}
}
public void notLambdaWithClass() {
	List list = Arrays.asList("a", "b", "c");
	list.replaceAll(new Operation());
	System.out.println("list:" + list.toString());
}

2.匿名クラスを使う
1.のサンプルを匿名クラス化します。
共通処理を行うたびにクラスを作っていては記述も大変ですし、処理を追うのも難しくなってきます。
そこで「Operation」を匿名クラスとして定義します。

public void notLambdaWithAnonymousClass() {
	List list = Arrays.asList("a", "b", "c");
	// 匿名クラスを用いる
	list.replaceAll(new UnaryOperator(){
		@Override
		public String apply(String t){
			return "[" + t + "]";
		}
	});
	System.out.println("list:" + list.toString());
}

3.匿名クラスをLambda式で記述する
本題のLambda式で記述します。
匿名クラスの記述がとても簡素になります。

public void lambda() {
	List list = Arrays.asList("a", "b", "c");
	// Stringの引数を1つ持つメソッド、戻り値は引数tを[]で囲ったもの
	// 他のサンプルと処理結果は代わりません。
	list.replaceAll((String t) -> "[" + t + "]");
	System.out.println("list:" + list.toString());
}

Lambda式の基本構文は以下のようになります。
(引数リスト) -> 処理リスト
引数リストは複数の引数を渡すことも可能です。
例えば
(String t1, String t2) -> …
などと記述できます。
引数がない場合は
() -> …
と記述します。
特定条件ではカッコも省略できます。
処理リストにもルールが有ります。
基本文法は
{
処理
return構文
}
となります。
処理がreturn処理の1行の場合、{}を省略できます。
その場合はreturn自体も省略します。

4.型推論を使う
3.のサンプルを更にシンプルにします。
Java8では型推論が強化されたので、引数リストの型を省略できます。

protected void lambdaWithInference() {
	List list = Arrays.asList("a", "b", "c");
	// 引数tの型を省略
	list.replaceAll(t -> "[" + t + "]");
	System.out.println("list:" + list.toString());
}

引数リストの型省略はは、すべての引数に対して行うか、すべて行わないかになります。
(t1, String t2)のような記述は行えません。

4.のサンプルまで来るとだいぶシンプルになります。

しかし、上記のサンプルの流れを見ていただくと気がつくかもしれませんが、Lambda式はJava7までの文法を使ってできることの記述法を変えたものになります。
なぜLambda式が必要になるか?という疑問があるかもしれません。
私も最初はその疑問を持っていたので調べてみました。

色々理由は書かれていたのですが、一番多かったのはCollectionの分散処理化に関するものでした。
Java7までのCollectionの分散処理はプログラマが分散処理のコードを書かなければいけませんでした。
Java8では各要素を処理するメソッドをCollectionに持たせ、利用することができます。
この際、処理メソッドを無名メソッドを用いて定義を行うのですが、Java7までの記述法で記載すると不格好かつ見辛い形になってしまいます。
この問題を解決し、より簡単、簡潔に無名メソッドを記述できるようにLambda式が導入されたと見られています。
Oracleの公式見解でもマルチコアによる並列処理を導入しやすくするためとあるようです。

Listの分散処理をJava8で実装してみました。
5.のサンプルは非分散処理、6.のサンプルは分散処理となります。
キーは6.のparallelStream()になります。
こちらの利用によって、分散処理をCollectionに任せることができるようになります。

5.forEach()を用いているが、分散処理ではない

public long lambdaAndParallelOff(List list) {
	long start = System.currentTimeMillis();
	list.forEach(x->{
		System.out.println(x + ":" + (System.currentTimeMillis() - start));
		try {
			Thread.sleep(1000); // 1処理ごとに1000msの待機
		}
		catch(InterruptedException e){
			// NOP
		}
	});
	long end = System.currentTimeMillis();
	return end - start;
}

6.parallelStream()を利用して分散処理化している

public long lambdaAndParallelOn(List list) {
	long start = System.currentTimeMillis();
	list.parallelStream().forEach(x->{
		System.out.println(x + ":" + (System.currentTimeMillis() - start));
		try {
			Thread.sleep(1000); // 1処理ごとに1000msの待機
		}
		catch(InterruptedException e){
			// NOP
		}
	});
	long end = System.currentTimeMillis();
	return end - start;
}

非常に簡単に分散処理の実装が行えます。

(2)Stream API

大雑把に言うと「Collection操作用API」でしょうか。
Java7までのCollection各要素の抽出は、Iteratorなどのループを用いて各要素を調べ、
抽出したい条件と一致するかを調べていたと思います。
Stream APIでは上記のLambda式との併用で、要素の抽出条件や処理方法を記載したメソッドをStream APIに渡し、結果を受け取るだけとなります。

こちらもCollectionに関わるものですが、Java7でできなかったことをできるようにするものではなく今まで一手間掛かっていた記述方法を簡易的に行えるようになるといったところです。
こちらのサンプルは手順を追って説明すると長くなるので、別の機会で個別にやりたいと思います。

(3)Date and Time API

既存のDate、Calendarを潰すつもりで作成されたらしい、新しい日付時刻APIです。
Java7までのDate、Calendarは使いづらい割に低機能でしたが、新しい日付時刻APIはより使いづらくはなったのですが、高機能な日付時刻APIとなっております。

新APIではLocal、Offset、Zonedの3つの基本的なクラスが用意されています。
Localはシステム日時、Offsetはタイムゾーンを意識しない日時、Zonedはタイムゾーンを意識した日時を扱います。
最近のアプリはグローバル展開が多くなっているので、ZonedDateTimeを使うのが一般的になるのでしょうか。

// basic 3API + JapaneseDate
System.out.println("----- Date and Time API -----");
System.out.println("LocalDateTime :" + LocalDateTime.now().toString());
System.out.println("OffsetDateTime:" + OffsetDateTime.now().toString());
System.out.println("ZonedDateTime :" + ZonedDateTime.now().toString());
System.out.println("JapaneseDate  :" + JapaneseDate.now().toString());

// LocalDate and LocalTime
System.out.println("LocalDate:" + LocalDate.now().toString());
System.out.println("LocalTime :" + LocalTime.now().toString());

こちらはまだ調査段階なのですが、気をつけなくてはいけない点があったので挙げました。
それは「既存Dateとの互換性の低さ」です。
上でも書きましたが、そもそも既存のDate、Calendarを潰すつもりと言われているだけあり少し変換を試したところでExceptionがバンバン発生して、なかなか思うような結果が得られませんでした。
また、パースフォーマットが厳密化されており、クラスの切り替え以外にも十分な注意が必要になります。

System.out.println("----- date format change -----");
// use DateTimeFormatter
System.out.println("formatter:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yy/MM/dd")));

Java7以前の曖昧なフォーマット変更を行うと、すぐにExceptionが発生します。
扱いには注意が必要です。(以前行えたyy/MM/ddaaaaみたいなフォーマットはNGです。)

このAPIは端的にいうと、高機能化した代わりに融通は効かなくなった新しい日付APIです。
java.util.Date から java.time.ZoneDateTimeの変換ですが、以下のように行います。

// Date → ZonedDateTime 
try {
	String sampleDate = "2014/01/01";
	Date date =  DateFormat.getDateInstance().parse(sampleDate);

	// Date → Instant → ZoneDateTime
	ZonedDateTime zdt = date.toInstant().atZone(ZoneId.systemDefault());
	System.out.println("ZonedDateTime:" + zdt.toString());
}
catch (ParseException e) {
	// NOP
}

一度Instantを経由したり、なかなか面倒になっています。

その他の新機能

今回は特に注目したい機能をピックアップしましたが、他にも色々気になる点があります。
JVMビルドの改善や、既存クラスに追加されたメソッドなどです。
Stringクラスに追加されたjoinメソッドなどは、システム開発に携わっていたら誰もが自作したことがあるのではないでしょうか。
joinメソッドは新しく導入されたStringJoinerを使っているので、この辺りも読んでおきたいものです。
このへんはリリースノートを眺めたり、javadocコメントからjava8に関するものを抜き出してぼちぼち追いかけて行きたいと思います。

最後に

システム開発の現場では、最新のJavaがリリースされたからといってすぐに乗り換えることはそうそうありません。
なので、新しい技術を現場で使う機会というのはなかなか訪れなかったりします。
ですが、知っていて使わないのと、知らないで使えないのでは大きな差があると思います。
大きなコストとリスクを負ってでも最新バージョンに切り替えたほうが良い場合も出てきます。
(私の場合は単純に好奇心で調べているだけですが…)

私がJava8を調べていたのは昨年の6~9月位なので、内容が変わっている可能性もありますが
次の機会に関連記事を書きたいと思います。その際にはStreamAPIのサンプルなどもご紹介したいと思います。




広告システムについてのお問い合わせやご相談、パッケージ製品の詳細はこちらからどうぞ。
http://admage.jp/
アプリ計測SDK admage for Appのお問い合わせ・詳細はこちら。
http://apptizer.jp/

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る

コメント

コメントを残す

*