概要
Java10から、varを使ったローカル変数の型推論が使えるようになりました。便利である反面、使い方を誤ると可読性が落ちる恐れがあるため、どのような場合に使うべきなのか考えてみました。(Java10が出たのが1年以上前なので今更な話ですが)
動作環境
Java10以降
そもそもvarって何?
ローカル変数の宣言時、変数の型を指定する代わりに「var」と書くことで、型の指定を省略することができます。いわゆる型推論です。
// 従来の書き方
String s = "hogehoge";
// varを使った書き方
var s = "hogehoge";
// StringクラスのメソッドであるtoUpperCase()を呼べる(変数sはString型と認識されている)
System.out.println(s.toUpperCase()); // => HOGEHOGE
何がうれしいの?
どんなに型名が長くても「var」と書くだけで済むため、書くのが楽になります。また、冗長な情報がなくなってすっきりするので、読むのも楽になるはず。
// 従来の書き方
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
// varを使った書き方
var dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
DateTimeFormatterというそこそこ長い名前が2回も出現していたのが1回になり、書くのも読むのも少し楽だと思います。
動的型付けではない!
varを使っても静的型付けであることに変わりはありません。変数に型がなくなるわけではなく、ただ変数の定義時に型が自動的に決定されるだけです。一度varで定義された変数に、別の型の値を再代入することはできません。(コンパイルエラーになる)1
// 右辺が文字列リテラルであるため、変数sは自動的にString型になる
var s = "hogehoge";
// sはStringなので、intなど別の型の値は代入できない
s = 123;
// エラー:
// 不適合な型: intをjava.lang.Stringに変換できません:
// s = 123;
// ^-^
また、varで変数の定義時に右辺から型を一意に特定できない場合もコンパイルエラーです。
// nullでは何の型なのかわからないためコンパイルエラー
var v = null;
// エラー:
// ローカル変数vの型を推論できません
// (変数初期化子は'null'です)
// var v = null;
// ^-----------^
JavaScriptのvar等とは全く違いますので、誤解なきように。
varを使うべきかどうか
varとは何なのかわかったと思います。(たぶん)
ここから、掲題の通りどんな場合にvarを使うべきか、使わないべきかについて考えていきます。
varを使うべき場合
一言でいえば、右辺を見れば一目で何の型かわかるような場合です。
具体的には下記のような場合が想定されます。
右辺でインスタンス化している場合
// 右辺に型名がそのまま書いてあるので、何の型か一見してわかる
var date = new Date();
var scanner = new Scanner(System.in);
var list = new ArrayList<String>();
右辺がリテラルである場合
// 右辺が文字列リテラルであるため、String型であると一見してわかる
var s = "hogehoge";
// 右辺が整数リテラルであるため、int型であると一見してわかる
var n = 123;
右辺で戻り値の型を容易に推測できるメソッドを呼び出している場合
// メソッド名が型名を含んでいるのでわかる
var br = Files.newBufferedReader(Paths.get("filename"));
// 現在日時の取得だが、LocalDateTimeのstaticメソッドなのでLocalDateTime型だろうと推測できる
var date = LocalDateTime.now();
// Calendarのインスタンスを取得するわけなので当然Calendar型だろうと推測できる
var cal = Calendar.getInstance();
varを使うべきではない場合
varを使うべき場合の逆ですが、一言でいえば右辺を見ても戻り値の型を正確には推測できないメソッドを呼び出している場合です。
// Date#getTime()
// 日付を表す型はいろいろあるので「Time」だけではわからない(実際はlong型)
var time = date.getTime();
// Path#getFileName()
// ファイル名だからStringかな?(実際はPath型)
var filename = path.getFileName();
// File#getAbsolutePath()
// PathとあるからこれもPath型?(実際はString型)
var path = file.getAbsolutePath();
// Files#copy(Path, OutputStream)
// なんか受け取ってるけど何なのかわからない。数値っぽいからint?(実際はコピーしたバイト数を表すlong型の値)
var size = Files.copy(path, out);
おまけ
無名クラスと組み合わせて使うことで、特定のメソッド内でのみ有効なメソッドを定義することができます。本題とは関係ないのですが、便利かもしれないので書いておきます。
public class Main {
public static void main(String[] args) {
// func.capitalize() で呼び出せる
// func はローカル変数なので、このメソッドの外からは呼べない
var func = new Object() {
private String capitalize(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1, s.length()).toLowerCase();
}
};
System.out.println(func.capitalize("tokyo")); // => Tokyo
System.out.println(func.capitalize("TOKYO")); // => Tokyo
}
}
まとめ
- 右辺を見て型が容易にわかる場合はvarを使ってOK
- 右辺を見ても型がわからない、または型を勘違いするような場合はvarを使わないほうがいい
- おまけとして、varを使えばメソッド内でメソッドを定義するっぽいことができる
参考リンク
- https://sun0range.com/event-report/jjug-java10-var
- https://qiita.com/hollydad/items/1516a3f13147a754b9db
- https://qiita.com/chooyan_eng/items/f01c336359bc08cfa4b0
-
もちろん、サブクラスなど代入互換性のある型の場合はこの限りではありません。 ↩