似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

お詫びと補足

お詫び

昨日の記事ですが、IntStream の状態で一度 count をかますと、コメントに残していた一覧表示(forEach の部分)が不可能になります。お詫びして修正します。今回は個数だけを求めるプログラムに特化する形にさせていただきました。一覧も表示させる場合は、元のゆっちさんの書いたように、一度 boxed から collect して List 型にしておく必要があります。

補足

長くなるので折りたたむ。

何であれで「Java8 すげぇ」って結論になるのか

昨日の記事を読まれた方は、「何であれだけで『Java8 すげぇ』って結論になるの ?」と疑問に思われた方がいらっしゃるかと思いますので、補足をさせていただきます。

簡単に並列化できる

並列処理自体は別に目新しいことではなく、過去のバージョンの Java でも可能でした。しかし並列化の処理を書くのが結構面倒臭い。しかし、Stream に限ってはメソッド一つかますだけであっさりと並列化できてしまう。裏を返せば、何か並列処理したいものがあるとき、Stream を上手く使えば簡単に並列化可能ということである(と思う)。

絞り込みの記述に関する可読性が高い

filter メソッドを使って絞り込みをしていく際、その条件に関する可読性が高い。具体的にどうやって絞り込まれていくのかがわかりやすい。

処理が早い

ベタだけどこれ重要。その理由は「中間処理」と「終端処理」の概念にある。Stream には「中間処理」と「終端処理」の概念があり、中間処理に関しては、実際に後で行う処理に関する手続を保持しただけで実際の処理がまだ行われていない Stream を返すだけなので、中間処理そのものに関する処理時間は劇的に短い。今回だと filter が中間処理に当たる(オリジナル版の boxed も中間処理)。これら中間処理は、最後に終端処理が呼び出された段階でまとめて実行されるので処理時間の短縮につながるという理屈だ。ただしこれにはデメリットがあって、終端処理を一度行ってしまうと、もうその Stream は触れない。再び終端処理を行おうとすると例外が発生してしまうのだ(見事に嵌りました…orz)。Stream を使って何かを処理するときはこの点を常に意識する必要がある。

こうしたメリット(いくつかのデメリットは承知の上で)により、私は「Java8 すげぇ」と率直に感じたのである。

ソースコードに関する補足

オリジナル版を読んでいて「これは何を意味するコードなんだろう ?」という部分が私にもあったので、プログラミングになじみの薄い方にもわかるように解説をさせていただきます。

このフィルタリングの結果は ?
.filter(n -> ((n & 0b1) != 0 || n == 0b10) && n > 0b1)

まず

(n & 0b1) != 0

の部分。0b10b が頭に付いているので 2 進数を表す。つまりこの式は「n1 のビットごとの and 演算をした結果が 0 ではない」という意味になる。1 をビット表記すると(Javaint 型は 32 ビットなので)

00000000000000000000000000000001

になる。これと and 演算して 0 にならないためには 20 の位が 1 じゃないとダメ。それは具体的には奇数です。つまり「n は奇数」っていうのが上の式の意味。

次に

n == 0b10

は右辺を 10 進数に直すと 2 なので

n == 2

と同じ。同じく

n > 0b1

n > 1

と同じ。

というわけで、フィルタリングの条件をまとめると「n は奇数または 2 で、かつ 1 より大きい」となるので、結果得られるのは

2, 3, 5, 7, 9, 11, ... (以下奇数がズラリ)

となる。1 と 2 より大きい偶数は絶対素数にならないので、最初からチェックの対象から外すわけです。

1 で割った余りって何ぞ !?
(ns * 1e-9) % 1

ナノ秒単位の時間を 1e-9 (= 10-9) 倍すると単位が「秒」になります。値は double 型で得られますが、それを 1 で割った余りとして「小数点以下の値」が得られます。つまりこの式は「秒未満の時間を秒単位で」求める式です。