【JDK 10】varを使った型推論

この記事は[Java Advent Calendar 2017](https://qiita.com/advent-calendar/2017/java)の16日目です。

JDK 10 で追加される予定の[JEP 286: Local-Variable Type Inference](http://openjdk.java.net/jeps/286)について、その内容と使い方について考えてみます。

# （2018.03.22 追記） JDK10正式版での動作確認について
2018年3月20日に、JDK10の正式版がリリースされましたので、この記事を書いたころから何か変更が入っていないか確認しました。

```
C:\Users\chooyan>"C:\Program Files\java\jdk-10\bin\javac.exe" -version
javac 10
```

結果、動作確認したり以下のページを見る限り、特にこの記事に記載した内容については変更無しのようでした。

[Java 10 Local Variable Type Inference | Oracle Developers](https://developer.oracle.com/java/jdk-10-local-variable-type-inference)

なお余談ですが、この記事のコメント欄で話題になった`var list = new ArrayList<>();`と書いてしまった場合の挙動について、上の記事でも触れられています。

結論としては`ArrayList<Object>`という扱いになってしまってメリットは特にないので避けてね、とのことです。

（追記ここまで）

# この記事の対象読者

この記事は、

* Javaを使って仕事をしていて
* まだ[JEP 286](http://openjdk.java.net/jeps/286)の内容は未チェックで
* Java以外の言語はあまり経験がない

という方を想定した記事です。

`var`はJava以外の言語でもよく出てくるキーワードのため、それらとの比較を期待される方もいるかと思いますが、そのあたりを書けるほどの知識と経験が僕にないため、この記事ではJavaのみに着目して新しく導入される予定の`var`を紹介するような内容となっています。

Javaの`var`についてざっくりと予習しておきたい方にとって最適な記事になるように書いていますので、それを踏まえた上で先を読み進めていただければと思います。

# 使用するJDK

この記事では[JDK 10 Early-Access Builds](http://jdk.java.net/10/)を利用します。

JDKをダウンロード・インストールし、`bin`フォルダ内の`javac`や`jshell`を利用することで、この記事のサンプルソースのように`var`を利用したソースコードを試すことができます。

```
C:\Users\chooyan>"C:\Program Files\java\jdk-10\bin\javac.exe" -version
javac 10-ea
```

# varの概要

[JEP 286: Local-Variable Type Inference](http://openjdk.java.net/jeps/286)で検討されているのは、変数宣言時の型指定に`var`を使うことで、変数宣言のためのコードを簡略化できるようにしましょう、というものです。

あれこれ説明する前に、サンプルコードを見てみましょう。なお、この記事では、数行で完結するサンプルコードには`jshell`を使います。

以下はint型の変数を宣言する例です。

```java:jshell
jshell> int limit = 20; // 従来の書き方でint型の変数を宣言
limit ==> 20

jshell> var limit = 20; // varを使ってint型の変数を宣言
limit ==> 20
```

`int`の型宣言が`var`に置き換わっています。

オブジェクト型についても同様です。いくつか見てみましょう。

```java:jshell
jshell> String name = "chooyan_eng"; // 従来の書き方でString型の変数を宣言
name ==> "chooyan_eng"

jshell> var name = "chooyan_eng"; // varを使ってString型の変数を宣言
name ==> "chooyan_eng"

jshell> List<String> idList = new ArrayList<>(); // 従来の書き方でList<String>型の変数を宣言
idList ==> []

jshell> var idList = new ArrayList<String>(); // varを使ってList<String>型の変数を宣言
idList ==> []
```

また、右辺がメソッドの場合も、そのシグニチャから戻り値の型が明確ですので`var`が使えます。

```java:jshell
jshell> private int sum(int x, int y) {
   ...>     return x + y;
   ...> }

|  次を作成しました: メソッド sum(int,int)

jshell> var s = sum(4, 5);

s ==> 9
```

いかがでしょうか。上記のように、__左辺の型指定を`var`に置き換えることで、タイプ量を減らしてコードを読みやすくする__というのがJEP286で検討されているJavaの新しい型推論です。

# varの制限

さて、新しい`var`を使うことで、左辺の型指定を簡略化できると書きましたが、いつでも`var`が使えるという訳ではありません。

`var`を使うためには、以下の条件を満たしている必要があります。

* メソッド内で宣言されるローカル変数である
* 右辺を見れば型が明白である

前提として、Javaの`var`は__「右辺を見れば左辺の型はコンパイル時に判別できるから、コーディング時には左辺の型指定を省略できる」__というものです。「実行時に型を判断する」という仕組みではありません。

もう少し詳しく説明すると、たとえば`Article`クラスのインスタンスを生成して`article`変数に代入する場合、今までは

```java:jshell
jshell> Article article = new Article();

article ==> Article@59494225
```

と書いていました。よく見ると、右辺で生成したインスタンスが`Article`型である以上、それを代入するための変数の型も`Article`型（もしくはその親クラス）でなければなりません。そして、そうでない場合にエラーにする、というのは現在もコンパイラが判断していることです。

であれば、左辺には`var`とだけ書いておいて、実際の変数の型はコンパイラが推論できるようにしちゃえば、1行に2回同じクラスを書く必要がなくなって便利だよね、というのがJavaの`var`の考え方です。

逆に、コンパイル時に右辺を見ても型が明確に決まらないような場面では`var`が使えません。ここからは、どのような場合に「右辺を見ても型が明確に決まらない」のかについて順番に説明していきます。

# 右辺を見ただけでは型が判断できない場合

右辺を見ただけでは型が判断できない場合はいくつかあります。

## 右辺がnull

変数宣言時、とりあえず`null`を代入しておく（もしくは何も代入しない）という状況はそれほど珍しくないのではないかと思います。

例えば

```java

private Article getArticle(int id) {
    Article article = null;

    if (isOnline()) {
        article = requestArticle(id);
    } else {
        article = selectArticleFromCache(id);
    }

    // article変数に対する何らかの処理

    return article;
}
```

というように、if文の中で代入する方法を変えたい、でもif文の外でもその変数にアクセスしたい、という場合には、if文の前で`article`変数を宣言し、nullで初期化する必要があります。

しかし、このとき

```java
Article article = null;
```

という変数宣言を

```java
var article = null;
```

と書くことはできません。なぜなら、この一行だけでは右辺がnullなため、変数`article`に何の型が代入されるのかをコンパイラが判断できないからです。

実際にjshellで試してみると、

```java:jshell
jshell> var article = null;

|  エラー:
|  cannot infer type for local variable article
|    (variable initializer is 'null')
|  var article = null;
|  ^--------------^
```

というように、nullでは型推論ができない旨のエラーが表示されます。

## ラムダ式とは併用できない

次に`var`が使えない状況として、ラムダ式により右辺の型を省略している場合があります。

例えば以下のような`Runnable`クラスを継承した匿名クラスを作る場合、現行バージョンのJavaではラムダ式をして以下のように記述できます。

```java:jshell
jshell> Runnable runner = () -> { System.out.println("run!"); };

runner ==> $Lambda$15/1471868639@343f4d3d
```

一方で、新しい`var`を使ってこれを書こうとすると、以下のようになります。

```java:jshell
jshell> var runner = () -> { System.out.println("run!"); };

|  エラー:
|  cannot infer type for local variable runner
|    (lambda expression needs an explicit target-type)
|  var runner = () -> { System.out.println("run!"); };
|  ^--------------------------------------------^
```

右辺のラムダ式に明示的な型指定がないため、左辺の`var`は型を判断できないよ、というメッセージがでています。つまり、ラムダ式による型の省略と`var`は同時に使えない、ということですね。

ちなみに、このようなケースは、以下のように型の変換を明示的に行うことで一応エラーを回避できます。

```java:jshell
jshell> var runner = (Runnable) (() -> {System.out.println("run!");})
runner ==> $Lambda$15/87765719@5442a311
```

とはいえ、これなら`var`を使わずに左辺で型を指定したほうが見やすいコードになるため、わざわざこのように書く必要はないでしょう。

```java:jshell
jshell> Runnable runner = () -> {System.out.println("run!");};
runner ==> $Lambda$15/87765719@5442a311
```

## インスタンス変数では使えない

先述したとおり、`var`は「ローカル変数」にしか使えません。つまり、以下のようにインスタンス変数に使おうとするとエラーになります。

```java:jshell
jshell> class Article {
   ...>     var id = 0;
   ...>     var title = "";
   ...> }

|  エラー:
|  'var' is not allowed here
|      var id = 0;
|      ^-^
|  エラー:
|  'var' is not allowed here
|      var title = "";
|      ^-^
```

エラーメッセージもそのままですね。「ここでは使えないよ」ということだそうです。

詳しくは分かりませんが、スコープが広くなってしまうと判断が難しくなるからローカル変数のみの制限になっている、ということでしょうか。このあたりは未調査ですが（すみません）、そういうものだそうです。

## 型宣言を省略した配列の初期化には使えない

以下のように、型宣言を省略して配列を作成する場合、`var`を使うことができません。

```java:jshell
jshell> var arr = {1, 2, 3}

|  エラー:
|  cannot infer type for local variable arr
|    (array initializer needs an explicit target-type)
|  var arr = {1, 2, 3};
|  ^------------------^
```

一方で`new int[]`で型を指定した場合は問題ありません。

```java:jshell

jshell> var arr = new int[]{1, 2, 3}
arr ==> int[3] { 1, 2, 3 }
```

タイプ量としては`var`を使わない方が少ないですが、ここは後者のような、型指定をした上で`var`で宣言するのが、他の宣言方法との統一性があってよいような気がしました。

# `var`導入に伴うFAQ

その他、`var`導入に伴って疑問に思われる（と思われる）内容を、一問一答形式で考えてみたいと思います。

## Q1. `final`にしたい場合はどうするの？

例えばKotlinの場合、再代入できる変数は`var`を、再代入できない変数には`val`を、というような使い分けがあります。

Javaではそのような「再代入できない`var`」をあらわすものは特に今のところ用意されていません。今まで通り`final`を型の前につけるだけです。

```java:VarSample.java
public class VarSample {
    public static void main(String[] args) {
        final var id = 2;
        id = 3;
        System.out.println(id);
    }
}
```

上記のコードをコンパイルすると、ちゃんと`final`をつけた変数には代入ができなくなっていることが分かります。

```
$ javac VarSample.java

VarSample.java:4: エラー: final変数idに値を代入することはできません
        id = 3;
        ^
```

じゃあ定数も`public static final var`と書けばよいのか、という話ですが、これはそもそも`var`がローカル変数にしか使えないという制限がありますので、定数は今まで通り

```java

public static final String DEFAULT_ID = "9999";
```

のように記述します。

ローカル変数につけられる修飾子は`final`だけですので、その他の修飾子との兼ね合いについて考えることはおそらくないでしょう。

## Q2. インターフェースで受け取りたいんだけど

`var`は、右辺から推論したとおりの型で右辺を受けうけとります。

つまり、今まで決まり文句のように書いていた、生成した具象クラスをインターフェースで受け取るということはできません。

```java:jshell
// 生成したArrayList<String>のインスタンスを、インターフェースであるList<String>で受け取る
jshell> List<String> idList = new ArrayList<>();  
```

```java:jshell
// varを使うと受け取る型を指定することができない
jshell> var idList = new ArrayList<String>();  
```

「オブジェクトはなるべくインターフェースで扱え！」という習慣が身についていると、これはとても大変な変更のように一瞬思ってしまいますが、実際はこれで不都合がでることはほとんどありません。

確かに、具象クラスで受け取ってしまうと以下のように、同じインターフェースを持つ別のクラスを代入する場合に不都合が発生します。

```java:jshell
jshell> var idList = new ArrayList<String>();
idList ==> []

jshell> idList = new LinkedList<String>();

|  エラー:
|  不適合な型: java.util.LinkedList<java.lang.String>をjava.util.ArrayList<java.lang.String>に変換できません:
|  idList = new LinkedList<String>();
|           ^----------------------^
```

しかし、もともとこのようなコードを書くことはほとんどないはずです。

そもそもインターフェースで扱うのは、メソッドの引数や戻り値としてどの具象クラスでも同じように扱えるようにする（必要に応じて具象クラスを切り替えられるようにする）ためです。

そして、そのような使い方は`var`でも変わらず可能です。

```java:jshell
jshell> private List<String> getIdList() {
   ...>     if (isArrayListSuitable()) {
   ...>         return new ArrayList<String>();
   ...>     } else if (isLinkedListSuitable()) {
   ...>         return new LinkedList<String>();
   ...>     }
   ...>     return new ArrayList<String>();
   ...> }
|  次を作成しました: メソッド getIdList()

jshell> var idList = getIdList();
idList ==> []
```

結局、`var`が自動的に具象クラスで受け取るようになるからと言っても、ローカル変数の宣言時にしか利用できない限り影響は限定的である、ということになります。

## Q3. すでに`var`って名前のクラス作っちゃったんだけど

これは問題です。なにが問題かというと、"var"という、Javaの命名規則から外れたクラス名を作ってしまったことが問題です。

実はJEP286でも、`var`を導入するリスクの一つとして"var"クラスがすでに定義されていた場合の互換性の問題が挙げられています。

> Risk: source incompatibilities (someone may have used var as a type name.)

しかし、そもそもクラス名として小文字始まりの単語、さらには他の言語でキーワードとして頻繁に使われる"var"という単語を型名としてつけることはほとんどないはずだから、"var"を新しい予約語とする方向は変わらない旨が書かれています。

>Mitigated with reserved type names; names like var do not conform to the naming conventions for types, and therefore are unlikely to be used as types. The name var is commonly used as an identifier; we continue to allow this.

すでに`var`というクラスを作ってしまった場合は、なんとかJDK 10に移行する前にリネームする必要があります。

なお、変数名の"var"は特に問題がありません。
少し読みづらいですが、キーワードの`var`と併せて使うことができます。

```java:jshell
jshell> int var = 1; // int型の変数varを定義
var ==> 1

jshell> var = 2; // 変数varに別の値を代入
var ==> 2

jshell> var var = 1; // varを使って変数varを定義（int型が推論される）
var ==> 1
```

# まとめ

以上、JDK 10で導入される予定の`var`について、現在リリースされているJDK10のearly access版を利用して確認してみました。

JavaScript等の言語をやっている人にとっては、`var`と聞くとJavaが動的に型が変化する言語になるような印象を受けるかもしれませんが、Javaの`var`とは「左辺の型指定を省略できる」程度のもので、特に挙動が変わるものではないことが伝わったのではないかと思います。

`var`が使えるようになると、今までとは違ったコーディング規約や慣習が出てくることが予想されますので、今のうちからイメージしておくことでスムーズに受け入れることができるのではないかと思います。ぜひJDK10のearly access版を落として動かしてみてください。

なお、まだ僕も[JEP 286](http://openjdk.java.net/jeps/286)に記載されている内容をちゃんと読んで理解したわけではないため、内容に一部誤りがあるかもしれません。何か変な点があればコメントいただければと思います。

---

# 宣伝

先日、[これからRubyで仕事したい人のためのリポジトリ「Code Your Ruby」を作りました](https://qiita.com/chooyan_eng/items/46f5724202f90ab4004d)という記事を書きました。

__「今はJavaエンジニアだけど、これからRubyを仕事にしてみたい」__ という方の勉強に最適な[Code Your Ruby](https://github.com/chooyan-eng/code-your-ruby)というリポジトリを紹介していますので、「それ自分だ！」と思った方はぜひ読んでみてください。