S-JIS[2018-04-21/2018-10-01] 変更履歴

Java var

Javaのvarについて。


概要

Java10でvarという構文が導入された。

ローカル変数を定義するとき、今までは「型 変数名 = 初期値」と書いてきたが、型の部分をvarで置き換えて「var 変数名 = 初期値」と書くことが出来る。
ローカル変数型推論(Local-Variable Type Inference)と呼ばれる。

「ローカル変数型推論」の名の通り、ローカル変数を定義するときだけ使える。
(フィールド定義では使えない)


たまに「Java10で型推論が導入された」と言う人がいるが、正しくない。
Javaの型推論は以前のバージョン(少なくともジェネリクスが導入されたJDK1.5)から存在している。
Java10で導入されたのは「ローカル変数の型推論」である。


varを使うと、一見すると変数の型が分からなくなる為、右辺(「=」の右側)から型が簡単に分かるときだけ使用するのが良いという意見がある。
一方、型が分からないような事はあまり無いので、ローカル変数には常にvarを使ってよいという意見もある。

C#ではだいぶ前にvarが導入され、同じような議論が起きたらしい。しかし結論がどうなったのか、誰も教えてくれないorz


JDK 説明
var s = "123"; // sはString
var n = 123; // nはint
  変数の型は右辺(「=」の右側)の型になる。
final var f1 = 123;
var final f2 = 123; // コンパイルエラー
  final変数にしたい場合はvarの左側にfinalを付ける。
(varの右側に付けるとコンパイルエラー)
JShellのコマンドライン(トップレベル)でfinalを書くと警告になる。
修飾子'final' は、トップ・レベル宣言で使用できません。無視されます」)
(Scalaだとvalを使う)
var obj; // コンパイルエラー   初期値を与えない書き方は出来ない。
var obj = null; // コンパイルエラー   nullを代入するときはvarは使用できない。
var func1 = () -> System.out.println("zzz"); // コンパイルエラー
var func2 = (Runnable) () -> System.out.println("zzz");
  ラムダ式を代入するときはvarは使用できない。
ただし関数型インターフェースでキャストしてやれば可能。
キャストするくらいならvarを使わず型名を書いた方がいいと思うが^^;
var func1 = String::valueOf; // コンパイルエラー
var func2 = (Function<Object, String>) String::valueOf;
  メソッド参照もラムダ式と同様。
var array1 = { 1, 2, 3 }; // コンパイルエラー
var array2 = new int[]{ 1, 2, 3 };
  配列の初期化構文ではvarは使用できない。
配列の型を明示すれば可。
public <T> T getT() { return 〜; }

var t = getT(); // tはObject
  右辺でジェネリクスの型引数が必要な場合、
型を明示しないとObjectになる。
(コンパイルエラーにはならない)
var list1 = new ArrayList<String>(); // list1はArrayList<String>
var list2 = new ArrayList<>(); // list2はArrayList<Object>
  コンストラクターでジェネリクスの型引数が必要な場合、
ダイアモンド演算子を使うとObjectになる。
(コンパイルエラーにはならない)
for (var i = 0; i < 10; i++) {
  System.out.println(i);
}

// ただし、複数の変数を定義することは出来ない
// for (var i = 0, j = 0; i < 10; i++)
  varはfor文try文でも使用できる。
var list = List.of("a", "b", "c");
for (var s : list) {
  System.out.println(s);
}
try (var is = Files.newInputStream(path)) {
  〜
}
package com.example.var;

public class Var {
  private String var = "field";

  public void var() {
    var var = "local";
    System.out.println(var);
    System.out.println(this.var);
  }

  public static void main(String... args) {
    var var = new Var();
    var.var();
  }
}
  varという語は「予約された型名」(Java10で新設された言語仕様)という扱いなので、
クラス名以外(すなわちパッケージ名・メソッド名・変数名)
で識別子として使用できる。
SourceVersionでもvarは識別子扱い。[2018-06-03]

もしJava9以前でvarというクラス名を使っていたら、
そのままJava10でコンパイルしたらエラーになる。
しかし慣例上、クラス名の先頭は小文字にしないので、
問題になる事はまず無いだろう。

var list = new ArrayList<String>();
list = new LinkedList<String>(); // コンパイルエラー
  varの型推論は、あくまで右辺しか使用しない。
変数が後続でどう使用されているかは関係ない。
左記の例で、もしlistがList<String>と推論されれば
コンパイルエラーにはならないのだが。
IntFunction<String[]> f = (var n) -> new String[n];
IntFunction<String[]> f = (@Annotation var n) -> new String[n];
11 Java11から、ラムダ式の引数にvarが付けられるようになった。[2018-10-01]
(varが使えて何が嬉しいかと言うと、アノテーションが付けられる)

無名内部クラスへの応用例

varを使うと、無名内部クラス(匿名クラス)の変数を作る事が出来る。
そして、その変数を使って匿名クラスのフィールドやメソッドにアクセスできる。

public static void method() {
	var object = new Object() {
		public int execute() {
			return 123;
		}
	};
	var result = object.execute();
	System.out.println(result);
}

匿名クラスを作る事自体はJava10より前でも出来た。
しかし匿名クラスではクラス名を明示できないので、変数を定義する事は出来なかった。 (「匿名クラスの元となったクラス」の変数として定義することは出来たが、匿名クラス独自のフィールドやメソッドにアクセスできない)
(以下のように、変数を使わずに匿名クラスのメソッドを呼ぶことは出来た)

public static void old() {
	int result = new Object() {
		public int execute() {
			return 123;
		}
	}.execute();
	System.out.println(result);
}

変数の定義方法の変遷

これは自分の推測だが。

Javaの変数の定義方法は、C++(C言語)の変数の定義方法から来ている。

C言語では「int n」や「struct MyStruct s」の様に、型名を変数名の前に書いて変数を定義していた。
初期値を書きたい場合は、「int n = 1;」「struct MyStruct s = { 2, 3 };」の様にする。
つまり、右辺には型名に相当するものが出てくることは無かった。

そしてC++でクラスが導入された。
今までの変数の宣言方法を踏襲すれば、当然「クラス obj」という書き方になる。
初期化する場合はどのクラスのインスタンスを作るかを表さないといけない為、「クラス obj = new クラス;」という書き方になり、クラス名が左辺と右辺の両方に現れることになってしまった。

JavaはC++の書き方を踏襲している。
特にJDK1.5(Java5)でジェネリクスが導入されてから、例えば「Map<String, Integer> map = new HashMap<String, Integer>();」の様に同じ型引数を2回書かなければならず、冗長だと言われるようになった。
これに対する解答がJDK1.7(Java7)で導入されたダイアモンド演算子である。これにより「Map<String, Integer> map = new HashMap<>();」と書けるようになった。

まぁそれでも他の言語ではvarの様に変数の型宣言を省略する言語が増えてきたので、Javaでもそれを望む声が出てきて
Java10でvarが導入されたのだろう。


ダイアモンド演算子は、右辺の型引数を省略(推論)するものである。
一方、varは左辺の型を省略(推論)するものなので、ダイアモンド演算子とは相性が悪い。
つまり「var map = new HashMap<>();」と書いたら、変数が何の型なのか分からない^^;
(実際に書くと、HashMap<Object, Object>と推論される)

Javaでは長らく、変数を定義するときに具象クラスをそのまま使うのではなく、抽象化されたクラス(インターフェース)を使うのが良いとされてきた。
つまり「ArrayList<String> list = new ArrayList<>();」より「List<String> list = new ArrayList<>();」の方が良いということだ。
ダイアモンド演算子はこの考え方に沿っていた。
varを使うと、変数の型は右辺の型になる。(変数が後続の処理でどう使われるかは考慮されない)
このため、「var list = new ArrayList<String>();」と書くと、変数の型は具象クラスArrayList<String>になる(List<String>にはならない)。

まぁ、ローカル変数はスコープが狭いので、インターフェースを使うべきか・具象クラスでよいのかをあまり厳密に考えなくてもいいんじゃないか、という事なのだろうか^^;
考え方は時代によって変わってくるので。


Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま