- JAVA
Java varの使い方と使えない場所一覧|エラー文つき早見表
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期連続の黒字決算。