Javaの上位互換性を信じすぎてはいけないとか

# クイズとか
いつものとおりクイズからスタートです。
以下のプログラムを**実行**して、得られる**結果**を答えてください。
```java
import java.util.stream.IntStream;
public class Peeker {

	public static void main(String[] args) {
		long i= IntStream
				.rangeClosed(1,3)
				.map(x->x*2)
				.peek(System.out::print)
				.count();
		System.out.println(i);
	}
}
```
ちなみにあまり見たことないかも知れませんので書いておきますと、peekは要素を渡すだけ、好きにしてね！という動きをします。
たぶんデバッグ用途以外で使うことはないです。てかJava APIリファレンスにもそう書かれてますし。
>This method exists mainly to support debugging,

# 答えとか
答えは **「Javaのバージョンによる」** です。
なんだってー（
ちなみにJava8では
```2463```
Java9からは
```3```
となります。

# 何が起こっているのかとか
このロジック、**終端処理がcount()** になっていますよね。
つまり、**要素数を数えているだけ**なのです。
そして、**map**は要素の中身を変えているだけで、**要素数を変更しません**。
**peekも同様**です。
よって、最終結果であるcount()を出すのに**mapもpeekも無駄な処理**ということになります。
**Java9から**は、この**無駄な処理が無視**されるようになったようです。
実際、
```java
		long i= IntStream
				.rangeClosed(1,3)
				.map(x->x*2)
				.filter(x->true)
				.peek(System.out::print)
				.count();
		System.out.println(i);
```
とかしてあげると、無視できなくなって
```2463```
という結果になります。

と、記事書いた後で気づいたのですが、この話は実は**Qiitaで既出**でした。
[JavaのStreamで変な挙動だなぁと思った話（2つ）](https://qiita.com/Iridescent_Null/items/7f7bf9fe597cb28bf2b4)
「count()と中間操作」参照

# 犯人は誰だとか
それではこの最適化は誰が行っているのでしょうか。
実は、私はclassファイル生成時に無駄な処理をカットするものだと思い込んでいました。
ところが、試しにjavap -c でアセンブルコード吐いてみたら

```(略)
  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: iconst_3
       2: invokestatic  #16                 // InterfaceMethod java/util/stream/IntStream.rangeClosed:(II)Ljava/util/stream/IntStream;
       5: invokedynamic #22,  0             // InvokeDynamic #0:applyAsInt:()Ljava/util/function/IntUnaryOperator;
      10: invokeinterface #26,  2           // InterfaceMethod java/util/stream/IntStream.map:(Ljava/util/function/IntUnaryOperator;)Ljava/util/stream/IntStream;
      15: getstatic     #30                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: dup
      19: invokevirtual #36                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      22: pop
      23: invokedynamic #40,  0             // InvokeDynamic #1:accept:(Ljava/io/PrintStream;)Ljava/util/function/IntConsumer;
      28: invokeinterface #44,  2           // InterfaceMethod java/util/stream/IntStream.peek:(Ljava/util/function/IntConsumer;)Ljava/util/stream/IntStream;
      33: invokeinterface #48,  1           // InterfaceMethod java/util/stream/IntStream.count:()J
      38: lstore_1
      39: getstatic     #30                 // Field java/lang/System.out:Ljava/io/PrintStream;
      42: lload_1
      43: invokevirtual #52                 // Method java/io/PrintStream.println:(J)V
      46: return
(略)
```
おおお？
**Java8でもJava9でも、書いたコードそのままにコンパイル**されてますね！
実際にJava8でコンパイルしたclassファイルをJava8で実行すると
```2463```
となりますが、Java9で実行すると
```3```
という結果が返ってきます。
つまり**javacが最適化したわけではない**のです。
そうすると犯人は奴しかありえません。
**JIT(Just In Time)コンパイラ**！
お前かーーっ！

# 真面目な話とか
ちょっと真面目な話をしておきますと、これ、実はかなり怖いことなんです。
最初に書いたとおり、peekなんてデバッグくらいにしか使わない(使っちゃいけない)のですが、開発環境がJava8で、実行環境はサポート対象のJava最新版とか、そういうケースがあるんですよ。
それで不具合の調査のために、とりあえずpeek入れて中身出力しとけや、みたいな乱暴なコードを無理矢理追加していたりすると、テスト環境ではデータが入っているのに本番環境ではデータが入っていない！みたいな恐ろしい勘違いを生むことが考えられます。
```java
//ヤバいコードの例
//long maxLength = list.length;
//list<Sting>の中身を表示するように変更
long maxLength = list.stream().peek(System.out::println).count();
```

# 最後にとか
**Java**はGUI系に手を出さない限り、**上位互換性が極めて高い言語**というのは、おおよそ納得してもらえるかと思います。
だからといって大丈夫と安心していると**思わぬ落とし穴**にハマります。
**バージョンが上がったら必ず動作確認**しましょう。