タイトルはふざけてますが、内容はまじめです。
とある案件でJava8 stream api使ったらsortでハマりましたという話。
何が起こったのかとか
簡単に言ってしまうと、ソートされるべき値がソートされずに出力されました。
なぜそうなったのかを考えてみます。
サンプルとか
ようはこういうことをしようとしたわけです。
IntStream
.range(0, 20)
.parallel()
.sorted()
.forEach(System.out::println);
で、結果。
12
5
1
0
8
2
4
3
10
16
7
13
18
6
11
14
17
15
9
19
見事にばらばらになりました。
この時点でわかる人には原因がわかると思います。
よーく考えればバーにもわかったはずですが、急ぎの仕事だったため、なんでsortedされてないの!とパニクってjavapで逆アセンブルして
0: iconst_0
1: bipush 20
3: invokestatic #16 // InterfaceMethod java/util/stream/IntStream.range:(II)Ljava/util/stream/IntStream;
6: invokeinterface #22, 1 // InterfaceMethod java/util/stream/IntStream.parallel:()Ljava/util/stream/IntStream;
11: invokeinterface #26, 1 // InterfaceMethod java/util/stream/IntStream.sorted:()Ljava/util/stream/IntStream;
16: getstatic #29 // Field java/lang/System.out:Ljava/io/PrintStream;
19: invokedynamic #38, 0 // InvokeDynamic #0:accept:(Ljava/io/PrintStream;)Ljava/util/function/IntConsumer;
24: invokeinterface #39, 2 // InterfaceMethod java/util/stream/IntStream.forEach:(Ljava/util/function/IntConsumer;)V
29: return
うわ、意味なかった!
なぜソートされないのかとか
これ、原因は単純な話でした。
IntStream
.range(0, 20)
.parallel()
.sorted()
.boxed()
.collect(Collectors.toList())
.stream()
.forEach(System.out::println);
これだと正しい結果になります。
つまり、sortedされていなかったわけではなく、sortedされていたのです。
にも関わらず、sortされていない出力になった理由はparallelにあります。
このparallel処理がforEachにも適用され、System.out::printlnがparallelに実行されたため、こういう結果になったのではないかと思われます。
実際に確認してみましょう。
今回調べて初めて知ったsequential!
こいつを使います。
IntStream
.range(0, 20)
.parallel()//パラレルを
.sorted()
.sequential()//シリアルに
.forEach(System.out::println);
実行すると正しい答えが得られます。
やはりforEachがparallel処理されたためにsortedされていないように見えた、ということで良さそうです。
なぜこれが初心者向けなのかとか
今回行った手順は
- sortedされていない!
- 本当にsortedされていないのか一度Listにして検証
- sortedされているのに正しい結果が得られない理由を推測
- sequentialを入れたコードを書いて検証
- 確定
となっており、これはバーが普段行っているトラブルシューティング手順そのものです。
何かおかしな結果が得られたときに、一番重要なのはもちろん原因となる場所を特定することですが、まれに「たまたま原因が特定できたように見える」ケースがあります。
今回のケースでも、検証しなければsortedが効いてない→sortedは使えないという結論になってしまっていたでしょう。
原因を特定した!
それは本当ですか?
検証しましたか?
このことが頭に入っていることが初心者脱出の一つの鍵ではないでしょうか。