Java8には型推論があるので型指定不要で変数が使えますよ

Javaプログラマのみなさんは、Java型推論がないから変数の型指定をしなくていけなくてダサい、などとイジメられた経験があると思います。
おかあさんに型推論をねだるとGroovyをわたされたり、おとうさんに型推論をねだるとScalaがやってきたり、プレステが欲しいって言ったのにWiiXboxを買い渡される感を味わった人も多いのではないでしょうか。
そんな良い子のJavaプログラマのために、今年はサンタが素敵なプレゼントを持ってきてくれましたよ。

同じ型を書くのがダサい

たとえばウィンドウを出してボタンを押したらメッセージが表示されるサンプルを書くとこんな感じになりますね。

public static void main(String... args){
    JFrame f = new JFrame("テスト");
    JButton b = new JButton("押して");
    JTextArea ta = new JTextArea();

    f.add(b, BorderLayout.NORTH);
    f.add(ta, BorderLayout.CENTER);

    b.addActionListener(ae -> 
        ta.append("押された\n")
    );

    f.setSize(400, 300);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setVisible(true);
}


動かすとこんなウィンドウが出ます。


変数定義はこんな感じになってます。

    JFrame f = new JFrame("テスト");
    JButton b = new JButton("押して");
    JTextArea ta = new JTextArea();

JFrame、JButton、JTextAreaが2回ずつ出てて、やっぱダサい。
入力はいろいろ補完が効くので、2回入力する必要はないんですけど。なので、2回入力するのが面倒という批判は的外れです。見た目がダサいのです。個人的には「new JFrame("テスト");」と打って、「新しい変数に戻り値を割り当て」とするのが好みです。

Let's let.

ということでサンタがくれたプレゼント。
こんなメソッド

public static<T> void let(T value, Consumer<T> cons){
    cons.accept(value);
}


このメソッドを使うと、さっきのコードはこうなります。

public static void main(String[] args) {
    let(new JFrame("テスト"), f -> 
    let(new JButton("押して"), b->
    let(new JTextArea(), ta ->{
        f.add(b, BorderLayout.NORTH);
        f.add(ta, BorderLayout.CENTER);

        b.addActionListener(ae -> 
            ta.append("押された\n")
        );

        f.setSize(400, 300);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    })));
}


変数の型指定がなくなりました!
これで、おまえんち型推論がないとか言ってイジメられることはなくなりますね!

戻り値が必要な場合

ところで、先ほどのletでは、値が必要になる場合に対応できません。
ということで、値を返す版としてletRを定義します。

static<T, R> R letR(T value, Function<T, R> func){
    return func.apply(value);
}


そうすると、こんな感じで平方根の計算ができます。

System.out.println(
    letR(2, target ->
        DoubleStream.iterate(target, x -> 
            letR(x * x - target, num ->
            letR(2 * x, den ->
                x - num / den
            )))
            .skip(5).findFirst().orElse(0)
    )
);


結果はこんな感じに。

1.4142135623730951


これ素敵なのは、式の途中で変数が使えることですね。
関数型っぽいです。


式の内容については、ここらへんが参考になるかと。
BigDecimalで平方根を求めてみる

型推論には限界がある

平方根のコードでもうひとつ変数を導入してみます。

System.out.println(
    letR(2, target ->
        DoubleStream.iterate(target, x -> 
            letR(x * x - target, num ->
            letR(2 * x, den ->
            letR(num / den, frac ->
                x - frac
            ))))
            .skip(5).findFirst().orElse(0)
    )
);


そうするとコンパイルエラーになってしまいます。

不適合な型: ラムダ式の戻り型が不正です
    推論型が上限に適合しません
      推論: R
      上限: Double,Object


どうも、ターゲット型推論が2段くらいまでしか見てない感じで、denかfracのletRの前にキャストを入れて明示的に型を指定してあげると大丈夫です。

System.out.println(
    letR(2, target ->
        DoubleStream.iterate(target, x -> 
            letR(x * x - target, num ->
            letR(2 * x, den ->
            (double)letR(num / den, frac ->
                x - frac
            ))))
            .skip(5).findFirst().orElse(0)
    )
);

型推論が3つ続かなければ大丈夫っぽい。b120で試してます。

まとめ

禁止ワード:プレステ3をねだったらプレステ1がやってきた感


おとなしくLombok

val f = new JFrame("テスト");
val b = new JButton("押して");
val ta = new JTextArea();

ってやるのが便利。エディタが対応してくれれば。

※2020/10/18 Java 10からは普通にvarでローカル変数型推論ができるようになってますよ。