きしだのはてな このページをアンテナに追加 RSSフィード

2018-03-26(月) Java10のvarをどう使うか

[][]Java10のvarをどう使うか 18:42 Java10のvarをどう使うかを含むブックマーク

Java 10でvarが追加されました。が、いろいろ使い方は悩ましい気がします。

いろいろ議論をしたので、そこで考えたことをまとめておきます。

JShellでは読むことを考えなくていいのでガンガン使いましょう。

あと、OpenJDKのStyle Guidelinesも見ておくとよさげ。

Style Guidelines for Local Variable Type Inference in Java


ローカルメソッドの定義にvarを使う

通常の変数定義でのvarの使い分けは長くなりそうなので、最初にこれだけ書いておきます。

いままで、メソッド内で使えるローカルメソッドを使おうと思うと、Functional Interfaceを使うくらいしかありませんでした。

Function<Integer, Integer> twice = x -> x * 2;
println(twice.apply(4));

しかし、通常のメソッド定義とは構文が違いすぎたり、思うようなthrowsがなかったり、プリミティブ型が使えなかったり、呼び出しで処理名とは別に余計なメソッド呼び出しだったりと、あまりいいものとは言えませんでした。


varでローカル変数で型推論をしてくれるようになると、匿名クラスを型として匿名のまま使えるようになり、それを使ってローカルメソッドを定義できるようになります。

var m = new Object() {
  int twice(int x) {
    return x * 2;
  }
};
println(m.twice(4));

ローカル変数でのvarの主な使い分け

varの使い分けとして、大きく5通りあると思います。

  1. varを使わない
  2. 右辺に型が書いてあるときだけvarを使う
  3. 右辺に型が書いてあるときプラスαでvarを使う
  4. 明示する型を決めてそれ以外はvarを使う
  5. ローカル変数は原則すべてvar

プラスαするよりは明示する型を決めることのほうが多い気がしますね。


右辺に型が書いてあるときだけvarを使う

ここで、varを部分的に使う場合にも、右辺に型が書いてある場合には使えるということになると思います。

ここで、右辺に型が書いてある場合というのは、大きく3つあります。

List<String> strs = new ArrayList<>(); // new
List<String> params = (List<String>) attr.get("params"); // キャスト
List<String> areas = List.of("fukuoka", "tokyo", "kyoto"); // ファクトリ

newやキャストでは必ず右辺に型が書かれます。ファクトリやビルダーも多くの場合は目的の型のクラスにstaticメソッドとして用意されていることが多いと思います。

そのような場合は、右辺を見れば型がわかり、左辺に型を書くと同じ表記が重複することから、このような場合はvarを使ったほうがいいと思います。

var strs = new ArrayList<String>(); // new
var params = (List<String>) attr.get("params"); // キャスト
var areas = List.of("fukuoka", "tokyo", "kyoto"); // ファクトリ

このように書いても、変数の型をコードから読み取ることができます。


なぜvarを使いたくない場合があるか

読む人数のほうが書く人数よりも多いため、書きやすさよりも読みやすさを優先したい場合が多いです。

IDEで見えるという話もありますが、コードを読むのはGithubなどWeb上が多いので、IDE前提でコードを書きにくいというのもあります。

変数名やメソッド名を型がわかりやすいものにするという主張は、それなら型を書いた方が確実なのでは、という気がします

Javaの場合はIDEで補完できるので、書く手間というのはあまりないですね。


varを使わないほうがいい型

ラッパークラスや数値のプリミティブ型は、型を把握しておいたほうがいいと思います。特にラッパークラスの場合は、不用意な演算でぬるぽが出るので、ラッパークラスであることを明示しておいたほうがいいと思います。


次のようなコードでは、演算が行なわれていることからsizeは数値の型であることがわかります。ただ、もしここで返ってくる型がラッパークラスであれば、掛け算の前にnullチェックが必要です。

var size = getFloorSize();
print(size * 100);

getFloorSize()がnullを返さないのであれば、ラッパークラスではなくプリミティブを返すべきだし、Java8以降であればOptionalを返しましょう、ということにはなりますが。

数値の場合は精度も明示されていたほうがいいと思います。


あと、Optionalもそうですが、最近は値がそのまま返って来ずに、なんらかのクラスで包まれて返ってくることも多くあります。このような場合も型は明示したほうがいいように思います。


次のようなコードがあるとき、果たしてこれはいいでしょうか。

var size = getFloorSize();
print(size.get() * 100);

たとえばこれがOptionalであれば、不用意にget()を呼び出してはいけません。

OptionalInt size = getFloorSize();
print(size.get() * 100);

Java10からはget()を使わずorElseThrow()を使った方がいいかもしれません。


Supplierであれば、get()を呼び出しても構わないと思います。

Supplier<Integer> size = getFloorSize();
print(size.get() * 100);

CompletableFutureだったら、ちょっと考えてしまいますね。

CompletableFuture<Integer> size = getFloorSize();
print(size.get() * 100);

これらをメソッド名や変数名で表すというのも、あまり考えたくないというか、そのまま型を書いたほうが確実で余分なルールを作らなくてすみます。

ということで、値を包むような型に関しては、varではなく型を明示したほうがいいように思います。


ラムダのパラメータでの型推論はいいのになぜvarがだめなの?

という話もありましたが、実際はラムダもあまりよくないことが多くありました。

そこからvarはあまり使わないほうがいいのではということにつながっています。

CompletableFutureやOptionalの場合、メソッドチェーンも避けたい場合があります。


ラムダで、streamの場合は、ある程度は型が推測できていたというのもあります。

List<Member> members = getMembers();
List<Integer> scores = members.stream()
  .map(m -> m.getScore())
  .collect(toList());

また、この場合はメソッド参照を使えば型は明示的になりますね。


結局のところ統一されていればいいのでは

まあ結局のところ、コードの性質やチームの性質で力点は変わると思うので、ソースコードに関わる人で合意を取って、統一するというのが大事ではないかと思います。