現場で使える 最新 Java SE 7/8 速攻入門

ITProの連載、Java技術最前線 | 日経 xTECH(クロステック)などで有名なJava Championの櫻庭さん(@skrbさん)からご著書、「現場で使える 最新 Java SE 7/8 速攻入門」を献本いただきました。

現場で使える[最新]Java SE 7/8 速攻入門

現場で使える[最新]Java SE 7/8 速攻入門

感想

この本は、Javaを普段使っている、もしくはJavaの基本は理解している人に向けた本だと思います。
そういった方で、Java SE 7/8を勉強したことが無い人は是非手に取ることをお勧めします。
特に、Javaの基本について書いた本ではJava SE 7や8の部分は少ししか触れられていないので、そういった書籍で勉強したばっかりの人にとっては絶対に読んでおきたい一冊です。

分かりやすい説明、かつ語りかけるような文章で書かれていて、すらすらと読めてとっても分かりやすいです。それはまるで櫻庭さんの講演を聞いているかのようです。
それでいて、かなり詳しい部分までしっかり書かれているので、この一冊をマスターすれば、かなりJava SE 7/8の特徴を使いこなせるようになると思います。

「はじめに」にも書かれているのですが、この書籍は各章ごとにほぼ独立しています。
そのため、前から順番に読むだけではなく、今興味のある部分から読み進めるという読み方ができて、気軽に読めます。
また,手元に一冊おいておいてJava SE 7/8で書くときに、気になる部分についての章を読みながら、プログラミングするという使い方もできるかと思います。

気になったところ

リスト3.43インスタンスメソッド参照の構文(p.128)
[オブジェクトを指定できる場合]
オブジェクト名::メソッド名

オブジェクトを指定できる場合,コロンの前にはオブジェクトを表す変数を記述します.

Consumer<String> consumer1 = texts::add;

オブジェクトを指定する場合は変数だけではなく,式を評価した値を使うこともできるので,変数やオブジェクト名以外も指定可能です.

Consumer<String> consumer1 = getTexts()::add;

上記のようにも記述可能です.この場合はgetTexts()の戻り値がメソッドレシーバとして使われます.*1

リスト3.41 (NG)チェック例外をスローするメソッドリファレンス(p.127)
// NG. チェック例外をスローするためコンパイルエラーになる
Consumer<Path> consumer = Files::delete;

(略)
このように,チェック例外をスローするメソッドはメソッド参照で記述できないので,注意が必要です.
チェック例外をスローする場合は,メソッド参照ではなく,ラムダ式で記述するようにしましょう.

チェック例外をスローするメソッドをメソッド参照で記述することは一応できる.
今回の場合はConsumer#acceptのthrows節に何も書かれておらず,メソッドのシグネチャの互換性が無いためエラーになっている*2

なので,throws節に書かれている例外についての互換性があれば,チェック例外をスローするメソッドをメソッド参照で記述できる.
Ideone.com - QrGFnZ - Online Java Compiler & Debugging Tool

interface I<T> {
    void accept(T t) throws IOException;
}

I<Path> consumer = Files::delete;

実際のところはj.u.functions.*で定義されている関数型インターフェースのメソッドは軒並み例外を投げる事は考えられていないので,事実上書けないと言えなくはないが・・・w

パラレルストリームとCollector(pp. 201-202)
Collector<String, StringBuilder, String> collector = Collector.of(
	() -> new StringBuilder(),
	(builder, s) -> builder.append(s),
	(builder1, builder2) -> builder1.append(builder2),
	builder -> builder.toString());
String result = texts.stream().collect(collector)

 ところで,このCollectorオブジェクトはパラレルストリームで使用することが出来ません.
というのも,ストリームの先頭から処理されることを想定しているからです.
 パラレルストリームではストリームの先頭から処理されるとは限りません.
しかし,文字列は途中から始まってしまっては意味をなさなくなってしまいます.
このため,パラレルストリームでは使用できないのです.
しかし,例えば,合計を求めるなどの処理は,要素の順番には依存しないので,パラレルストリームでも扱うことができます.
 このようにCollectorインターフェースの実装クラスごとにパラレルストリームで扱えるかどうかという性質が変わってきます.
この性質を表すのがCollector.Characteristics列挙型です.

CONCURRENT パラレル処理のサポート

 ofメソッドで生成するCollectorオブジェクトは,デフォルトではパラレルストリームをサポートしていません.
パラレルストリームでも使用できるようにするためには,CONCURRENTを指定します.

ここの部分は実はかなり違っています.
上記のCollectorオブジェクトはパラレルストリームでも利用できます.
しかも,順序を守った状態で並列に実行できます*3

パラレルストリームのcollectでは二通りの方法で処理されます.

最も多くの場合で用いられる手法はreduce手法での処理です.
reduce手法での処理の場合,n個のスレッドを起動させて,各スレッドごとにsupplierを用いて結果オブジェクトを生成します.
前述の場合は,「() -> new StringBuilder()」を実行して,StringBuilderを作成します.
これをそれぞれスレッドごとにr1〜rnと呼ぶことにする.
その後はスレッドmが担当する値をaccumulatorとrmを用いてそのスレッド内での結果を作成します.
最後に,combinerを用いてr1〜rnを統合して,全ての値に対しての結果rを作成します.
finisherが与えられている場合は,それを用いてrから別の値を作成します.

ちなみに,順序を守らなければならない場合*4は,r1〜rnを統合する際にr1とr2を統合して,その結果とr3を統合して,・・・その結果とrnを統合するため,パラレルストリームで処理しても順序が守られています.
前述のCollectorの処理もパラレルストリームではreduce手法で処理されています.

もうひとつの方法がforEachを用いる方法です.
この場合は,suppliderを用いて1つの結果オブジェクトrを生成します.
そして,n個のスレッドを起動させて,各スレッドが担当する値をaccumulatorとrを用いて結果に格納していきます.
この時,accumulatorは複数のスレッドからr(1つの結果オブジェクト)に対して呼び出されるため,スレッドセーフでなければ行けません.
この場合は,各スレッドでの処理が終わった段階で,結果オブジェクトは1つしか無いため,combinerは用いられません.
finisherが与えられている場合は,それを用いてrから別の値を作成します.

どちらの手法で実行されるかを決定するための要素(実際にはそれ以外の要素も関係している)がCollector.Characteristics列挙型です.
列挙型の各値は次を意味します.

CONCURRENT accumulatorを同じ結果オブジェクトに対して複数のスレッドから呼び出しても問題無い事を示す
IDENTITY_FINISH (書籍は正しいため説明略)
UNORDERED (書籍は正しいため説明略)

パラレルストリームのcollectでforEachを用いる方法が用いられる場合は,与えられたCollectorがCONCURRENTで,Stream自体がunorderedか与えられたCollectorがUNORDEREDの場合のみです.
それ以外の場合はreduceの手法が用いられます.
そのため,基本的にはどのCollectorオブジェクトでも,適切に実装されていればパラレルストリームで使用することが出来る*5

ちなみにこのことはOpenJDKのStreamの実装を読むことで分かる.
jdk8/jdk8/jdk: 687fd7c7986d src/share/classes/java/util/stream/ReferencePipeline.java

    public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
        A container;
        if (isParallel()
                && (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
                && (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
            container = collector.supplier().get();
            BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
            forEach(u -> accumulator.accept(container, u));
        }
        else {
            container = evaluate(ReduceOps.makeRef(collector));
        }
        return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
               ? (R) container
               : collector.finisher().apply(container);
    }
4.7.1 (Optionalの)生成(p.210)
ofNullable(T value) nullの場合もありうる値を保持したOptionalオブジェクトを生成する

ofメソッドとofNullableメソッドは指定した値を保持するOptionalオブジェクトを生成します.
両者の違いはofメソッドはvalueがnullになることを禁じているのに対し,ofNullableメソッドはnullでもかまわないという点です.

言及されていないのですが,ofNullableにnullを渡した場合はempty()を呼び出し時と同じになります.
つまり,値が無い状態のOptionalオブジェクトが帰ってきます.
Optionalクラスでは値が存在していて,その値がnullという状態を表すことは出来ません.
若干紛らわしいかなと思った.

4.7.4 Optionalクラスの用途(pp. 216-217)

ローカル変数にOptionalクラスを使うのは、明らかにやり過ぎです

何かしらのメソッドがOptionalの値を返す場合に、それをローカル変数に一時的に保存するのは、仕方が無いのでその場合は良いと思う。
もちろんそれ以外の場合のことを指した文だとは思うけれど、もう一言あると優しいような。*6

*1:かなり細かい話だった・・・

*2:For each checked exception type X listed in the throws clause of the invocation type of the compile-time declaration, X or a superclass of X must be mentioned in the throws clause of the function type of U, or a compile-time error occurs.

*3:Ideone.com - WVBjbs - Online Java Compiler & Debugging Tool出力はかなり長いが,最初の出力で順序がバラバラなのに対し,生成したオブジェクトの出力は順序が守られている.適切にパラレルストリームで実行できている

*4:Collector.Characteristics.UNORDEREDが与えられていない,またはStreamがorderedの場合

*5:combinerがデタラメといった,適切に実装されていないものは,仕方がないけど,当然パラレルストリームでは使えない.

*6:これもかなり細かい話だった