- JEP 286: Local-Variable Type Inference
- ローカル変数宣言型推論の注意点
- 「インターフェースを使ったプログラミング」についてはローカル変数では重視しなくてよい
- ダイヤモンド<> やジェネリックメソッドにおける var 利用は注意すること
- リテラルにおける var 利用は注意すること
- var の利用例
- まとめ
JEP 286: Local-Variable Type Inference
Java 10 によりローカル変数宣言で型推論(type inference)が可能となり var
により型定義を省略できるようになりました。
C++ での auto
、C# での var
、Scala での var
/val
、Go での :=
のように大抵の言語ではローカル変数型推論をサポートしており、Java は2018年になりようやく使えるようになりました。
以下のようなローカル変数宣言が可能になります。
var reader = Files.newBufferedReader(...); var stringList = List.of("a", "b", "c");
final var
とすることで再代入不可にすることができます。
final var string = "hello";
コンパイル後のバイトコードレベルでは、従来型の型宣言との違いはありません。
var
が使える箇所は以下となります。
- 初期化子有りのローカル変数宣言
- 拡張 for ループのインデックス
- 従来型 for ループのインデックス変数宣言
クラスフィールドやメソッド引数には使えませんし、右辺の型が推測できないような初期化には利用できません。
具体的には以下の箇所で var
は使用できません。
- クラスフィールド
- メソッド仮引数
- コンストラクタ仮引数
- メソッド戻り値型
- catch 仮引数
さらに、以下のようなラムダ式では使えません。
// NG
var runner = () -> { };
以下のようにすれば使えますが、
// OK
var runner = (Runnable) () -> { };
素直に以下のように書いた方がわかりやすいですね。
Runnable runner = () -> { };
ローカル変数宣言型推論の注意点
例えば以下の記載は何を行っているのかが不明瞭です。
var results = userService.find(q);
この場合は以下のように型を明示した方がわかりやすいでしょう。
List<User> results = userService.find(q);
なんでもかんでも var
を使うのは良くない使い方であり、ガイドラインとしては Local Variable Type Inference: Style Guidelines があります。
4つの原則
- 書きやすさより読みやすさを常に優先せよ
- 型推論は局所的なスコープで使い明確なコードにせよ
- IDE に依存した可読性はさけよ
- 明示的な型宣言と常にトレードオフを考えよ
7つのガイドライン
- 意味を伝える変数名を選択する
- ローカル変数のスコープは出来るだけ小さく
- 初期化子から十分な意味が読み取れる場合に
var
を使う - メソッドチェーンやネストされた式は
var
によ中間変数で分割する - 「インターフェースを使ったプログラミング」についてはローカル変数では重視しなくてよい
- ダイヤモンド
<>
やジェネリックメソッドにおけるvar
利用は注意すること - リテラルにおける
var
利用は注意すること
いくつか分かりにくいものもあるので以下に説明します。
「インターフェースを使ったプログラミング」についてはローカル変数では重視しなくてよい
インスタンスは通常該当するインターフェースで受けます。
List<String> list = new ArrayList<>();
しかし var
の場合は実装型が推論されます。
var list = new ArrayList<String>();
list
は ArrayList<String>
として推論されることになります。
これは「インターフェースを使ったプログラミング」に反するようですが、var
はローカル変数にのみ利用でき、通常は狭いスコープでしか使わないため変更などによる後続コードへの影響は限定的です。
ダイヤモンド<>
やジェネリックメソッドにおける var
利用は注意すること
以下の例はいずれも PriorityQueue<Item>
を扱うので問題ありません。
PriorityQueue<Item> itemQueue = new PriorityQueue<>(); var itemQueue = new PriorityQueue<Item>();
しかし以下の場合はPriorityQueue<Object>()
と型推論されるので注意が必要です。
var itemQueue = new PriorityQueue<>();
リテラルにおける var
利用は注意すること
以下のリテラルを var
に変えた場合、
byte flags = 0; short mask = 0x7fff; long base = 17;
以下は全て int に推論されます。
var flags = 0; var mask = 0x7fff; var base = 17;
var
の利用例
以下の例は初期化子から十分な意味が読み取れます。
try (InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is, charsetName); BufferedReader buf = new BufferedReader(isr)) { return buf.readLine(); }
このようなコードは var
を使うことでノイズが減り、可読性が向上します。
try (var inputStream = socket.getInputStream(); var reader = new InputStreamReader(inputStream, charsetName); var bufReader = new BufferedReader(reader)) { return bufReader.readLine(); }
ジェネリクスによるメソッドの柔軟性は、結果として冗長となり可読性が悪くなることがあります。
void removeMatches(Map<? extends String, ? extends Number> map, int max) { for (Iterator<? extends Map.Entry<? extends String, ? extends Number>> iterator = map.entrySet().iterator(); iterator.hasNext();) { Map.Entry<? extends String, ? extends Number> entry = iterator.next(); if (max > 0 && matches(entry)) { iterator.remove(); max--; } } }
var
により冗長な型宣言を無くすことで可読性が向上します。
void removeMatches(Map<? extends String, ? extends Number> map, int max) { for (var iterator = map.entrySet().iterator(); iterator.hasNext();) { var entry = iterator.next(); if (max > 0 && matches(entry)) { iterator.remove(); max--; } } }
まとめ
Java 10 で導入されたローカル変数宣言で型推論(type inference)について説明しました。
型情報はコードを読む際の補助になるケースもあれば、ノイズのように感じるケースもあります。
var
によるローカル変数宣言の型推論は、常に読みやすさとのトレードオフを意識して注意して使っていく必要がありますね。