Javaのvarとは、ローカル変数の型を右辺から自動で決めてもらう書き方(ローカル変数の型推論)です。 var list = new ArrayList<String>(); と書くと、コンパイラが list を ArrayList<String> 型として扱います。Java 10 で追加され、Java 11 からはラムダ式の引数にも書けるようになりました。型がなくなるわけではなく、コンパイル時に型が1つに決まり、その後は変えられない点は、型名を書いた場合とまったく同じです。

この記事では、var が「使える場所/使えない場所」を早見表にまとめ、使えない場所については javac が実際に出すエラー文を1つずつ再現しました(OpenJDK 21.0.10、日本語表示と英語表示の両方)。あわせて、var list = new ArrayList<>() が ArrayList<Object> になる罠、ラムダ引数の var、推論された型を確かめる3つの方法も、コピペして java ファイル名.java で試せる形で載せています。

varが使える場所・使えない場所の早見表

場所 書き方の例 使える? javac のエラー(日本語表示)
ローカル変数(初期化あり) var name = "ノート"; 使える ―
拡張 for 文 for (var item : list) 使える ―
ふつうの for 文 for (var i = 0; i < 3; i++) 使える ―
try-with-resources try (var r = new BufferedReader(...)) 使える ―
配列(new で型を書く) var a = new int[]{1, 2}; 使える ―
ラムダ式の引数(Java 11〜) (var a, var b) -> a + b 使える ―
匿名クラスを受ける var o = new Object() { int hit; }; 使える ―
フィールド class A { var count = 0; } 使えない 'var'はここでは許可されません
メソッド・コンストラクタの引数 int twice(var n) 使えない 'var'はここでは許可されません
メソッドの戻り値 static var create() 使えない 'var'はここでは許可されません
catch の引数 catch (var e) 使えない 'var'はここでは許可されません
型引数 List<var> list 使えない 'var'はここでは許可されません
record の構成要素 record P(var x, var y) 使えない 'var'はここでは許可されません
初期化なし var x; 使えない ローカル変数xの型を推論できません(初期化子なしで変数に'var'を使用することはできません)
null で初期化 var x = null; 使えない ローカル変数xの型を推論できません(変数初期化子は'null'です)
配列初期化子だけ var arr = {1, 2, 3}; 使えない ローカル変数arrの型を推論できません(配列初期化子には明示的なターゲット型が必要です)
ラムダ式を代入 var f = x -> x * 2; 使えない ローカル変数fの型を推論できません(ラムダ式には明示的なターゲット型が必要です)
メソッド参照を代入 var p = System.out::println; 使えない ローカル変数pの型を推論できません(メソッド参照には明示的なターゲット型が必要です)
複数同時宣言 var a = 1, b = 2; 使えない 'var'は複合宣言で許可されません
配列の [] と併用 var nums[] = new int[3]; 使えない 'var'は配列の要素タイプとして許可されません
戻り値が void のメソッド var r = System.out.println("a"); 使えない ローカル変数rの型を推論できません(変数初期化子は'void'です)
ラムダ引数で var と型なしを混ぜる (var a, b) -> a + b 使えない 無効なラムダ・パラメータ宣言

覚え方は1つです。「メソッドの中で、右辺を見れば型が1つに決まる変数」にだけ使えます。 フィールド・引数・戻り値はメソッドの外から見える「約束」なので型を書く、null・ラムダ・{1, 2, 3} は右辺だけでは型が決まらないので書けない、と整理できます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java varの使い方と使えない場所一覧|エラー文つき早見表」

varの基本の使い方|コピペで動くサンプル

前提:Java 10 以降(この記事は OpenJDK 21.0.10 で確認)。次のファイルを VarBasic.java として保存し、java VarBasic.java で実行します。

import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class VarBasic {
    public static void main(String[] args) throws Exception {
        // 1. ローカル変数(初期化あり)
        var title = "在庫一覧";                       // String
        var count = 3;                                // int
        var price = 1280.5;                           // double
        var names = new ArrayList<String>();          // ArrayList<String>
        var stock = new HashMap<String, List<Integer>>(); // HashMap<String, List<Integer>>
        names.add("ノート");
        names.add("ペン");
        stock.put("ノート", List.of(10, 20));

        // 2. 拡張 for 文
        for (var name : names) {
            System.out.println("name=" + name.toUpperCase() + " len=" + name.length());
        }
        // 3. ふつうの for 文
        for (var i = 0; i < count; i++) {
            System.out.print(i + " ");
        }
        System.out.println();
        // 4. Map のループ(型名が長いところで効く)
        for (var entry : stock.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
        // 5. try-with-resources
        try (var reader = new BufferedReader(new StringReader("1行目\n2行目"))) {
            System.out.println(reader.readLine());
        }
        // 6. 配列(new で型を書けば使える)
        var nums = new int[]{1, 2, 3};
        System.out.println(nums.length);
        // 7. final と組み合わせ
        final var limit = 100;
        System.out.println(title + " " + price + " " + limit);
        // 8. var は予約語ではないので変数名にもできる(おすすめはしない)
        var var = "変数名もvar";
        System.out.println(var);
    }
}

実行結果:

name=ノート len=3
name=ペン len=2
0 1 2 
ノート -> [10, 20]
1行目
3
在庫一覧 1280.5 100
変数名もvar

ポイントを整理します。

  • for (var entry : stock.entrySet()) のように、型名が長くなる場所ほど効果が大きい。型を書くと Map.Entry<String, List<Integer>> になります。
  • var で宣言しても、name.toUpperCase() のように その型のメソッドは補完もコンパイルチェックも効く。型が消えるわけではありません。
  • final var と書けば再代入できない変数になります。Java には JavaScript の const や Kotlin の val に当たるキーワードはなく、final var がその役割です。
  • var は「予約語」ではなく「制限された型名」という扱いで、変数名やメソッド名には使えます(8 の結果)。クラス名には使えません(後述)。

型が決まった後は変えられない

var count = 10;
count = "ten";
ErrReassign.java:4: エラー: 不適合な型: Stringをintに変換できません:
        count = "ten";
                ^

JavaScript の var とは別物で、Java の var は「型を書くのを省略しただけ」です。count は int に決まっているので、文字列は代入できません。

推論される型の一覧と確認方法

リテラルや式ごとの推論結果

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class VarType {
    public static void main(String[] args) {
        var a = 10;
        var b = 10L;
        var c = 10.0;
        var d = 10.0f;
        var e = 'A';
        var f = "A";
        var g = true;
        var h = new ArrayList<String>();
        var i = new ArrayList<>();
        var j = List.of(1, 2, 3);
        var k = Map.of("a", 1);
        var l = new int[]{1, 2};
        var m = 10 / 3;
        var n = 10 / 3.0;
        var o = true ? 1 : 2.0;

        // 実行時の型(基本型はラッパークラスで表示される)
        System.out.println("a = " + ((Object) a).getClass().getSimpleName());
        System.out.println("b = " + ((Object) b).getClass().getSimpleName());
        System.out.println("c = " + ((Object) c).getClass().getSimpleName());
        System.out.println("d = " + ((Object) d).getClass().getSimpleName());
        System.out.println("e = " + ((Object) e).getClass().getSimpleName());
        System.out.println("f = " + f.getClass().getSimpleName());
        System.out.println("g = " + ((Object) g).getClass().getSimpleName());
        System.out.println("h = " + h.getClass().getSimpleName());
        System.out.println("i = " + i.getClass().getSimpleName());
        System.out.println("j = " + j.getClass().getName());
        System.out.println("k = " + k.getClass().getName());
        System.out.println("l = " + l.getClass().getSimpleName());
        System.out.println("m = " + m + " (" + ((Object) m).getClass().getSimpleName() + ")");
        System.out.println("n = " + n + " (" + ((Object) n).getClass().getSimpleName() + ")");
        System.out.println("o = " + o + " (" + ((Object) o).getClass().getSimpleName() + ")");
    }
}

実行結果:

a = Integer
b = Long
c = Double
d = Float
e = Character
f = String
g = Boolean
h = ArrayList
i = ArrayList
j = java.util.ImmutableCollections$ListN
k = java.util.ImmutableCollections$Map1
l = int[]
m = 3 (Integer)
n = 3.3333333333333335 (Double)
o = 1.0 (Double)
右辺 推論される型 補足
10 int byte・short・long にはならない
10L long 大きな数を扱うなら L を付ける
10.0 double
10.0f float
'A' char
"A" String
new ArrayList<String>() ArrayList<String> List<String> ではなく実装クラスの型
new ArrayList<>() ArrayList<Object> 型引数の手がかりがないため Object(後述の罠)
List.of(1, 2, 3) List<Integer> メソッドの戻り値の型(実体のクラス名ではない)
10 / 3 int(値は 3) 整数どうしの割り算
10 / 3.0 double
true ? 1 : 2.0 double(値は 1.0) 三項演算子は両方を表せる型にそろう

getClass() で見えるのは「実行時に入っているオブジェクトのクラス」で、j は ImmutableCollections$ListN と表示されます。var が推論するのはコンパイル時の型(この場合は List<Integer>)なので、次の方法で確かめるのが確実です。

推論された型を確かめる3つの方法

方法 手順 向いている場面
1. IDE で確認 変数にマウスカーソルを合わせる(IntelliJ IDEA や Eclipse、VS Code の Java 拡張で型が表示される) ふだんの開発
2. jshell の /vars JDK 付属の jshell に貼り付けて /vars と入力 エディタなしですぐ確かめたいとき
3. わざと別の型に代入する int x = 変数; と書いてコンパイルし、エラー文の型名を読む どの環境でも使える

方法2:jshell の /vars(JDK 9 以降に同梱)。次の4行を貼り付けて /vars を実行した結果です。

var a = java.util.List.of(1, 2.5);
var b = new java.util.ArrayList<>();
var c = 10 / 3.0;
var d = java.util.Map.of("a", 1).entrySet();
|    List<Number&Comparable<? extends Number&Comparable<?>&java.lang.constant.Constable&java.lang.constant.ConstantDesc>&java.lang.constant.Constable&java.lang.constant.ConstantDesc> a = [1, 2.5]
|    ArrayList<Object> b = []
|    double c = 3.3333333333333335
|    Set<Map.Entry<String,Integer>> d = [a=1]

new ArrayList<>() が ArrayList<Object> になっていること、List.of(1, 2.5) のように整数と小数を混ぜると、「Number であり Comparable でもあり…」という名前で書けない型になることがわかります。

方法3:わざと別の型に代入する。

import java.util.List;
import java.util.Map;

public class Reveal {
    public static void main(String[] args) {
        var a = List.of(1, 2.5);
        var b = Map.of("a", List.of(1));
        var c = new Object() { int hit = 0; };
        var d = 10 / 3.0;
        int x1 = a;
        int x2 = b;
        int x3 = c;
        String x4 = d;
    }
}
Reveal.java:10: エラー: 不適合な型: List<INT#1>をintに変換できません:
        int x1 = a;
                 ^
  INT#1,INT#2がintersection型の場合:
    INT#1 extends Number,Comparable<? extends INT#2>,Constable,ConstantDesc
    INT#2 extends Number,Comparable<?>,Constable,ConstantDesc
Reveal.java:11: エラー: 不適合な型: Map<String,List<Integer>>をintに変換できません:
        int x2 = b;
                 ^
Reveal.java:12: エラー: 不適合な型: <anonymous Object>をintに変換できません:
        int x3 = c;
                 ^
Reveal.java:13: エラー: 不適合な型: doubleをStringに変換できません:
        String x4 = d;
                    ^
エラー4個

エラー文の「〜を int に変換できません」の「〜」が、var の推論結果です。確かめたら代入の行は消すのを忘れないようにしましょう。

varが使えない場所とエラー文、直し方

ここからは、早見表の「使えない」を1つずつ再現します。エラー文は javac -J-Duser.language=ja(日本語表示)の出力そのままで、英語表示のメッセージも併記します。

エラー文の日本語・英語対応表

日本語のメッセージ 英語のメッセージ 出る場面
'var'はここでは許可されません 'var' is not allowed here フィールド、引数、戻り値、catch、型引数、record の構成要素
ローカル変数xの型を推論できません cannot infer type for local variable x 初期化なし、null、配列初期化子、ラムダ、メソッド参照、void、自己参照
(初期化子なしで変数に'var'を使用することはできません) (cannot use 'var' on variable without initializer) var x;
(変数初期化子は'null'です) (variable initializer is 'null') var x = null;
(配列初期化子には明示的なターゲット型が必要です) (array initializer needs an explicit target-type) var arr = {1, 2, 3};
(ラムダ式には明示的なターゲット型が必要です) (lambda expression needs an explicit target-type) var f = x -> x * 2;
(メソッド参照には明示的なターゲット型が必要です) (method reference needs an explicit target-type) var p = System.out::println;
(変数初期化子は'void'です) (variable initializer is 'void') 戻り値のないメソッドを代入
(自己参照変数に'var'を使用することはできません) (cannot use 'var' on self-referencing variable) var x = x + 1;
'var'は複合宣言で許可されません 'var' is not allowed in a compound declaration var a = 1, b = 2;
'var'は配列の要素タイプとして許可されません 'var' is not allowed as an element type of an array var nums[] = ...、var[] nums = ...
無効なラムダ・パラメータ宣言 invalid lambda parameter declaration ラムダ引数で var と他の書き方を混ぜた

フィールドには使えない

public class ErrField {
    var count = 0;
    static var NAME = "RJC";
}
ErrField.java:2: エラー: 'var'はここでは許可されません
    var count = 0;
    ^
ErrField.java:3: エラー: 'var'はここでは許可されません
    static var NAME = "RJC";
           ^
エラー2個

フィールドはクラスの外や別のメソッドからも参照される「クラスの設計の一部」です。初期化の式を少し変えただけで型が変わると影響が広がるため、型を明示するルールになっています。

直し方:型を書きます。

public class Fixed {
    int count = 0;
    static final String NAME = "RJC";
}

メソッドの引数・戻り値には使えない

public class ErrParam {
    static int twice(var n) {
        return n * 2;
    }
    ErrParam(var name) {}
}
ErrParam.java:2: エラー: 'var'はここでは許可されません
    static int twice(var n) {
                     ^
ErrParam.java:5: エラー: 'var'はここでは許可されません
    ErrParam(var name) {}
             ^
エラー2個
public class ErrReturn {
    static var create() {
        return "abc";
    }
}
ErrReturn.java:2: エラー: 'var'はここでは許可されません
    static var create() {
           ^
エラー1個

引数には初期値がないので推論の手がかりがありません。戻り値も、呼び出す側との約束なので型を書きます。直し方は static int twice(int n)、static String create() のように型を明示することです。「いろいろな型を受け取りたい」が目的なら、var ではなくジェネリクス(static <T> T first(List<T> list))を使います。

catch の引数(catch (var e))、型引数(List<var>)、record の構成要素(record Point(var x, var y))も同じ 'var'はここでは許可されません になります。

初期化なし・null では型が決まらない

var x;
x = 10;
ErrNoInit.java:3: エラー: ローカル変数xの型を推論できません
        var x;
            ^
  (初期化子なしで変数に'var'を使用することはできません)
エラー1個
var x = null;
ErrNull.java:3: エラー: ローカル変数xの型を推論できません
        var x = null;
            ^
  (変数初期化子は'null'です)
エラー1個

var は宣言した行の右辺だけを見て型を決めます。 次の行の x = 10; までは見に行きません。null もどの型にも入れられる値なので、型の手がかりになりません。

直し方:後から値を入れる変数や null で始める変数は、型を書きます。

int x;                 // 後で代入するなら型を書く
x = 10;

String message = null; // null で始めるなら型を書く
var message2 = (String) null;  // キャストすれば var でも書けるが、上のほうが読みやすい

配列初期化子だけでは使えない

var arr = {1, 2, 3};
ErrArrayInit.java:3: エラー: ローカル変数arrの型を推論できません
        var arr = {1, 2, 3};
            ^
  (配列初期化子には明示的なターゲット型が必要です)
エラー1個

{1, 2, 3} は、int[] にも long[] にも double[] にもなれる書き方なので、これだけでは型が決まりません。

直し方:new int[] を付けます。

var arr = new int[]{1, 2, 3};   // int[] と推論される
int[] arr2 = {1, 2, 3};         // 型を書くなら {} だけでよい

また、var と [] を組み合わせる書き方もできません。

ErrArrayBracket.java:3: エラー: 'var'は配列の要素タイプとして許可されません
        var nums[] = new int[3];
        ^
ErrArrayBracket.java:4: エラー: 'var'は配列の要素タイプとして許可されません
        var[] nums2 = new int[3];
        ^
エラー2個

右辺が new int[3] なら、var nums = new int[3]; だけで配列型になります。

ラムダ式・メソッド参照はそのまま代入できない

var f = x -> x * 2;
var r = () -> System.out.println("run");
ErrLambda.java:3: エラー: ローカル変数fの型を推論できません
        var f = x -> x * 2;
            ^
  (ラムダ式には明示的なターゲット型が必要です)
ErrLambda.java:4: エラー: ローカル変数rの型を推論できません
        var r = () -> System.out.println("run");
            ^
  (ラムダ式には明示的なターゲット型が必要です)
エラー2個
var p = System.out::println;
ErrMethodRef.java:3: エラー: ローカル変数pの型を推論できません
        var p = System.out::println;
            ^
  (メソッド参照には明示的なターゲット型が必要です)
エラー1個

ラムダ式は、代入先の型(Function なのか UnaryOperator なのか、自作のインターフェースなのか)によって初めて型が決まる書き方です。「左辺が右辺から決まる var」と「右辺が左辺から決まるラムダ」を組み合わせると、どちらも相手を待つ形になるため、型が決まりません。

直し方:左辺に関数型インターフェースを書きます(次の章で実行例を示します)。

Function<Integer, Integer> f = x -> x * 2;
Runnable r = () -> System.out.println("run");
Consumer<String> p = System.out::println;

複数同時宣言はできない

var a = 1, b = 2;
ErrMulti.java:3: エラー: 'var'は複合宣言で許可されません
        var a = 1, b = 2;
        ^
エラー1個

直し方:1行に1つずつ宣言します。int a = 1, b = 2; のように型を書けば同時宣言できますが、1行1変数のほうが読み違いが起きにくくなります。

var a = 1;
var b = 2;

そのほか(void・自己参照・クラス名)

書き方 エラー文(日本語表示)
var r = System.out.println("a"); ローカル変数rの型を推論できません(変数初期化子は'void'です)
var x = x + 1; ローカル変数xの型を推論できません(自己参照変数に'var'を使用することはできません)
class var {} ここでは'var'は許可されません(リリース10から'var'は制限された型名であり、型の宣言に使用できません)

ダイヤモンド演算子とvarの罠|ArrayListの中身がObject型になる

var でいちばん気づきにくいのが、ダイヤモンド演算子(<>)との組み合わせです。

import java.util.ArrayList;
import java.util.List;

public class Diamond {
    public static void main(String[] args) {
        var list = new ArrayList<>();      // ArrayList<Object> と推論される
        list.add("文字列");
        list.add(123);                     // 数値も入ってしまう
        list.add(java.time.LocalDate.of(2026, 10, 3));
        System.out.println(list);

        Object first = list.get(0);        // 取り出すと Object
        System.out.println(first.getClass().getSimpleName());

        var fixed = new ArrayList<String>();   // 右辺に型引数を書く
        fixed.add("文字列");
        String s = fixed.get(0);
        System.out.println(s.length());

        List<String> typed = new ArrayList<>(); // 左辺に型を書くなら <> でよい
        typed.add("OK");
        System.out.println(typed);
    }
}

実行結果:

[文字列, 123, 2026-10-03]
String
3
[OK]

<> は「左辺の型から型引数を補ってください」という書き方です。左辺が var だと補う元がないため、いちばん広い Object で埋められます。 コンパイルは通り、文字列も数値も日付も入るリストになります。気づくのは、取り出して使おうとしたときです。

var list = new ArrayList<>();
list.add("ノート");
String s = list.get(0);
int len = list.get(0).length();
DiamondErr.java:7: エラー: 不適合な型: ObjectをStringに変換できません:
        String s = list.get(0);
                           ^
DiamondErr.java:8: エラー: シンボルを見つけられません
        int len = list.get(0).length();
                             ^
  シンボル:   メソッド length()
  場所: クラス Object
エラー2個

直し方は「型引数はどちらか片方に必ず書く」です。

書き方 推論される型 判定
var list = new ArrayList<>(); ArrayList<Object> 意図と違う型になりやすい
var list = new ArrayList<String>(); ArrayList<String> おすすめ(右辺に書く)
List<String> list = new ArrayList<>(); List<String> おすすめ(左辺に書く)
var map = new HashMap<>(); HashMap<Object, Object> 意図と違う型になりやすい
var map = new HashMap<String, Integer>(); HashMap<String, Integer> おすすめ

そのほかの気をつけたい推論結果

import java.util.ArrayList;
import java.util.List;

public class Trap {
    public static void main(String[] args) {
        // 罠1:小さい数値は int になる(byte / short / long にはならない)
        var small = 10;
        byte b = 10;
        System.out.println(((Object) small).getClass().getSimpleName() + " / " + ((Object) b).getClass().getSimpleName());

        // 罠2:整数リテラルは int。大きな値は L を付ける
        var total = 2_000_000_000;
        total += 2_000_000_000;      // int の範囲を超えて桁あふれ
        var totalL = 2_000_000_000L;
        totalL += 2_000_000_000;
        System.out.println(total + " / " + totalL);

        // 罠3:インターフェース型ではなく実装クラス型になる
        var list = new ArrayList<String>();   // ArrayList<String>
        List<String> iface = new ArrayList<>();
        list.ensureCapacity(100);             // ArrayList 固有のメソッドも呼べる
        // iface.ensureCapacity(100);         // List にはないのでコンパイルエラー
        System.out.println(list.size() + " " + iface.size());

        // 罠4:割り算は右辺の型で決まる
        var avg1 = 7 / 2;
        var avg2 = 7 / 2.0;
        System.out.println(avg1 + " / " + avg2);
    }
}

実行結果:

Integer / Byte
-294967296 / 4000000000
0 0
3 / 3.5
気をつけたい点 起きること 対応
var small = 10; int になる byte・short・long が必要なら型を書くか 10L
var total = 2_000_000_000; に加算 int の範囲を超えて -294967296 になった 合計値など大きくなる変数は long と書く
var list = new ArrayList<String>(); List ではなく ArrayList 型 後で LinkedList などに差し替えるなら List<String> と書く
var avg = 7 / 2; int の 3 小数が必要なら 7 / 2.0、または double avg と書く

3つ目について、var で宣言した変数に別の実装クラスを代入すると次のエラーになります。

TrapErr.java:7: エラー: 不適合な型: LinkedList<String>をArrayList<String>に変換できません:
        list = new LinkedList<String>();
               ^
エラー1個

ローカル変数の中だけで完結するなら影響は小さいのですが、「後から別の実装に差し替えるつもりの変数」は、インターフェース型を明示しておくと意図が伝わります。

Java 11以降|ラムダ式の引数にvarを書く

Java 11 から、ラムダ式の引数にも var が書けるようになりました(JEP 323)。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;

public class LambdaVar {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    @interface NotEmpty {}

    public static void main(String[] args) {
        // Java 11〜:ラムダの引数に var(全部に付ける)
        BiFunction<String, Integer, String> repeat = (var s, var n) -> s.repeat(n);
        System.out.println(repeat.apply("ab", 3));

        // var を付けると、アノテーションや final を書ける
        BiFunction<String, String, String> join = (@NotEmpty var a, final var b) -> a + "-" + b;
        System.out.println(join.apply("A", "B"));

        // 型を省略した書き方(Java 8〜)と結果は同じ
        IntBinaryOperator add1 = (x, y) -> x + y;
        IntBinaryOperator add2 = (var x, var y) -> x + y;
        System.out.println(add1.applyAsInt(1, 2) + " " + add2.applyAsInt(1, 2));

        // ラムダ自体を var で受けたいとき:左辺に型を書く、またはキャストする
        Function<Integer, Integer> twice = x -> x * 2;                 // おすすめ
        var twice2 = (Function<Integer, Integer>) x -> x * 2;          // キャストで型を渡す
        System.out.println(twice.apply(5) + " " + twice2.apply(5));

        // メソッド参照も同じ
        var printer = (java.util.function.Consumer<String>) System.out::println;
        printer.accept("メソッド参照もキャストすれば var で受けられる");

        // ラムダの「中」のローカル変数には普通に使える
        List.of("ノート", "ペン").forEach(name -> {
            var label = "[" + name + "]";
            System.out.println(label);
        });
    }
}

実行結果:

ababab
A-B
3 3
10 10
メソッド参照もキャストすれば var で受けられる
[ノート]
[ペン]

(x, y) -> x + y と (var x, var y) -> x + y は同じ意味です。var を付ける主な目的は、型名を書かずに引数へアノテーションや final を付けられることです。(@NotEmpty x, y) とは書けませんが、(@NotEmpty var x, var y) なら書けます。

ラムダ引数の var には「全部に付けるか、全部付けないか」というルールがあります。

BiFunction<Integer, Integer, Integer> f1 = (var a, b) -> a + b;
BiFunction<Integer, Integer, Integer> f2 = (var a, Integer b) -> a + b;
ErrLambdaMix.java:4: エラー: 無効なラムダ・パラメータ宣言
        BiFunction<Integer, Integer, Integer> f1 = (var a, b) -> a + b;
                                                   ^
  ('var'と暗黙的に型指定されたパラメータを組み合せることはできません)
ErrLambdaMix.java:5: エラー: 無効なラムダ・パラメータ宣言
        BiFunction<Integer, Integer, Integer> f2 = (var a, Integer b) -> a + b;
                                                   ^
  ('var'と明示的に型指定されたパラメータを組み合せることはできません)
エラー2個
ラムダ引数の書き方 可否 使えるバージョン
(a, b) -> ... 書ける Java 8 以降
(Integer a, Integer b) -> ... 書ける Java 8 以降
(var a, var b) -> ... 書ける Java 11 以降
(var a, b) -> ... エラー ―
(var a, Integer b) -> ... エラー ―
var a -> ...(かっこなし) エラー(かっこが必要) ―
(@NotEmpty a, b) -> ...(var なしでアノテーション) エラー ―

最後の2行も javac 21 でコンパイルし、どちらも構文エラー(「';'がありません」「式の開始が不正です」など)になることを確認しました。

匿名クラスをvarで受ける

匿名クラスの型には名前がないので、型名では宣言できません。var なら、匿名クラスに足したフィールドやメソッドをそのまま使えます。

public class Anon {
    public static void main(String[] args) {
        // 匿名クラスの型は名前で書けないが、var なら受けられる
        var counter = new Object() {
            int hit = 0;
            void up() { hit++; }
        };
        counter.up();
        counter.up();
        System.out.println("hit = " + counter.hit);

        // Object で受けると追加したメンバーは見えない
        Object obj = new Object() { int hit = 0; };
        // System.out.println(obj.hit);  // コンパイルエラー
        System.out.println(obj.getClass().getName());
    }
}

実行結果:

hit = 2
Anon$2

ラムダ式の中から外側のローカル変数は書き換えられませんが、この形ならカウンターのような「ちょっとした状態」をメソッド内だけで持てます。 使いどころは限られるので、「var でないと書けない型もある」という知識として押さえておけば十分です。

varを使うか型を書くかの判断表

var は「書く量を減らす」機能というより、「読むときに型名の繰り返しを減らす」機能です。右辺を見て型がすぐわかるかどうかを基準にすると、判断がぶれません。

場面 おすすめ 理由
new で生成(var list = new ArrayList<String>();) var 右辺に型が書いてあり、左辺は繰り返しになる
型名が長い(Map.Entry<String, List<Integer>> など) var 変数名に目が行きやすくなる
for 文・try-with-resources の変数 var スコープが短く、すぐ下で使う
リテラル(var count = 0;、var name = "";) どちらでも 型は明らかだが、省ける文字数は少ない
メソッドの戻り値(var result = service.find(id);) 型を書く、または変数名で補う 右辺から型が読み取れない
数値の計算(合計・平均など) 型を書く int か long か double かが結果に影響する
後で別の実装に差し替える 型(インターフェース)を書く var だと実装クラスの型に固定される
ダイヤモンド <> と一緒 どちらか片方に型引数を書く ArrayList<Object> を避ける
null や後から代入 型を書く var では書けない
flowchart TD
    A[ローカル変数を宣言する] --> B{初期値がある? null ではない?}
    B -- いいえ --> T[型を書く]
    B -- はい --> C{右辺を見て型がすぐわかる?}
    C -- わかる --> V[var を使う]
    C -- わからない --> D{変数名で中身が伝わる?}
    D -- 伝わる --> V
    D -- 伝わらない --> T

var を使うときほど、変数名を丁寧に付けるのがコツです。var x = repo.find(id); よりも var member = memberRepository.find(id); のほうが、型が書かれていなくても中身が伝わります。

バージョン別対応表

機能 使えるバージョン 確認した内容
ローカル変数の var Java 10 以降(JEP 286) javac --release 10 でコンパイルできた
ラムダ引数の var Java 11 以降(JEP 323) --release 10 ではエラー、--release 11 でコンパイルできた
record パターン内の var(case Point(var x, var y)) Java 21 以降(JEP 440) Java 21 で動作

javac 21 で --release 9 を指定して var x = 1; をコンパイルすると、次のようになりました。

V10.java:1: 警告: リリース10から、'var'は制限された型名であり、型の宣言での使用、または配列の要素タイプとしての使用はできません
V10.java:1: エラー: シンボルを見つけられません

Java 9 以前では var が「var という名前のクラス」と解釈され、そのクラスが見つからないというエラーになります。同じく --release 10 でラムダ引数に var を書いたときは次のメッセージでした。

V11.java:2: エラー: 暗黙ラムダのvar構文は-source 10でサポートされていません
  (暗黙ラムダのvar構文を有効にするには-source 11以上を使用してください)

「シンボルを見つけられません」で var を指しているときは、コードではなくコンパイルの対象バージョン(--release、Maven の maven.compiler.release、Gradle の toolchain など)を確認します。

よくある質問

Q. var を使うと実行速度は遅くなりますか? A. 変わりません。型はコンパイル時に決まり、型名を書いた場合と同じ扱いになります。実行時に型を調べ直すような動的型付けとは別の仕組みです。

Q. var はフィールドに使えますか? A. 使えません。'var'はここでは許可されません(英語では 'var' is not allowed here)というコンパイルエラーになります。使えるのは、メソッド内のローカル変数、for 文の変数、try-with-resources の変数、ラムダ式の引数(Java 11 以降)です。

Q. var list = new ArrayList<>(); はなぜ String を取り出せないのですか? A. 左辺が var だと <> を補う手がかりがなく、ArrayList<Object> と推論されるためです。list.get(0) の型は Object になります。var list = new ArrayList<String>(); と右辺に型引数を書くか、List<String> list = new ArrayList<>(); と左辺に型を書くと解決します。

Q. JavaScript の var と同じものですか? A. 別物です。Java の var は宣言時に型が1つに決まり、あとから別の型の値は代入できません(count = "ten"; は「不適合な型」のエラー)。スコープもふつうのローカル変数と同じブロック単位です。

まとめ

  • var は「右辺から型を決めてもらう」ローカル変数の書き方。型はコンパイル時に1つに決まり、後から変えられない。
  • 使えるのはメソッド内のローカル変数・for 文・try-with-resources・ラムダ引数(Java 11〜)。
  • フィールド・引数・戻り値・catch・型引数は 'var'はここでは許可されません。型を明示する。
  • 初期化なし・null・配列初期化子・ラムダ・メソッド参照は ローカル変数xの型を推論できません。かっこ内の補足メッセージで原因がわかる。
  • var list = new ArrayList<>(); は ArrayList<Object>。型引数は左右どちらかに必ず書く。
  • 推論結果は IDE、jshell の /vars、わざと別の型に代入したときのエラー文で確認できる。
  • 右辺を見て型がすぐわかるなら var、わかりにくいなら型を書く、と使い分けると読みやすいコードになる。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java varの使い方と使えない場所一覧|エラー文つき早見表」

株式会社RJCでは、Javaのvarと型推論の使いどころを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?