Java 中 var 类型推断有哪些使用限制和坑?
Java `var` 仅适用于局部变量、循环变量和 `try-with-resources` 资源变量,必须声明时初始化,不能赋 `null`、复合声明或使用数组简写;菱形泛型会推断为 `ArrayList` 等宽类型,需显式写全泛型或用带
学完这篇你能分清 Java var 在哪些位置能用、哪些位置会直接编译报错,以及推断出来的类型和你想的不一样时该怎么排查。
第一步:确认版本,把 var 的适用范围划清楚
这一步要做的是确认你的 JDK 支持 var,并明确它只能出现在局部变量的位置,后面所有写法都以此为边界。
java -version
javac -version
两个命令输出都 ≥ 10 就支持 var(Java 10 引入)。编译老项目或指定版本时用 javac --release 10 X.java;低于 10 的语言级别会报 cannot find symbol: var。
var 只允许用在四个位置:方法体内的局部变量、for 循环的初始化变量、for-each 的迭代变量、try-with-resources 的资源变量。不允许用在:字段、方法参数、构造器参数、方法返回值、catch 参数。
注意:
var不是关键字,而是「保留类型名」。var var = 1;能正常编译,也可以拿它当方法名,但不能当类名。团队代码规范里最好直接禁止这种写法。
第二步:初始化写法上最容易直接报错的几种情况
这一步要避开一批「写出来就编译不过」的语法,编译器在这里不给任何补救余地。
- 必须当场初始化:
var x;报错,因为推断不出类型。 - 不能赋 null:
var x = null;报错。 - 不能复合声明:
var a = 1, b = 2;报错,一行只能声明一个。 - 不能用数组初始化器简写:
var arr = {1, 2, 3};报错,必须写成var arr = new int[]{1, 2, 3};。 - 不能声明数组类型:
var[] arr = new int[3];报错。
var count = 0; // OK,int
var arr = new int[]{1, 2, 3}; // OK,int[]
var input = new ArrayList<String>(); // OK
try (var in = new FileInputStream("a.txt")) { } // OK
第三步:菱形泛型是最大的类型偏移坑
这一步解决 var 最常被投诉的问题:推断出来的泛型类型比你以为的宽。
var list = new ArrayList<>(); 推断结果不是 ArrayList<String>,也不是「待定」,而是 ArrayList<Object>。之后 list.add("abc") 能过,但 String s = list.get(0); 会编译失败,报 incompatible types: Object cannot be converted to String。
正确写法是两边都写全:var list = new ArrayList<String>();。用工厂方法则没这个问题,因为签名已经带泛型:var list = List.of("a", "b"); 推断为 List<String>。
注意:
var的类型来自方法或构造器的声明类型,不是运行时对象类型。var x = List.of("a")推断为List<String>,而不是内部的不可变实现类。
第四步:字面量和三元表达式会把类型推断成你不想要的样子
这一步确认运算符和条件表达式参与推断时的结果类型。
var i = 1; // int
var l = 1L; // long
var f = 1.0f; // float,写成 1.0 就是 double
var c = 'a' + 1; // int,不是 char
var sum = i + 1L; // long
var flag = cond ? 1 : "a"; 不会报错,但结果类型是 int 与 String 的公共父类型(可序列化且可比较的交集类型),你既不能当 int 用也不能当 String 用,只能当 Object 用。这类代码在 review 时几乎一定会被打回,直接拆成 if-else 更省事。
注意:
var变量一旦确定类型就不能改。var x = 1; x = "s";报错,它跟动态类型语言完全不是一回事。
第五步:lambda、方法引用、匿名类的边界
这一步区分哪些「没有独立类型」的表达式不能用 var,以及 var 在匿名类上的特殊好处。
- lambda 不能独立推断:
var f = () -> "x";报错,必须先有目标类型,写成var f = (Function<String, String>) s -> s;。 - 方法引用同理:
var f = String::valueOf;报错。 - lambda 参数可以用 var(Java 11 起):
BiFunction<Integer, Integer, Integer> f = (var a, var b) -> a + b;。但所有参数必须同时写 var,(var a, Integer b)这种混用会报错。加注解时也必须用 var,比如(@NonNull var a, var b)。 - 匿名类反而是 var 的亮点:
var obj = new Object() { void hello() {} }; obj.hello();合法。用显式类型写Object obj = ...就调不到hello(),这是var少见的净收益。
另外,var 变量在 lambda 里被引用时,同样要满足 effectively final,跟普通局部变量一致。
第六步:把可读性约定写进团队规范
这一步把上面结论落成可执行的规则,避免 var 变成代码审查的扯皮点。
建议约定三条:一、只在「右侧一眼能看出类型」时用 var,例如 var user = userRepository.findById(id);;二、泛型集合一律写全 new ArrayList<String>(),不用裸菱形;三、var 变量名要承担类型信息,var d = ... 这种命名直接不通过。
注意:
var编译后与显式类型生成的字节码完全一致,不影响性能,也不影响反射拿到的字段类型(因为字段本来就不能用 var)。顾虑性能是多余的。
小结
var只能用于局部变量、for 循环变量、for-each 变量、try-with-resources 资源,字段、参数、返回值一律不行。- 必须当场初始化,不能赋
null,不能复合声明,不能用{1,2,3}简写数组。 var list = new ArrayList<>();会推断成ArrayList<Object>,泛型要写全。- 三元表达式和混合数值运算的推断结果常出人意料,宁可显式声明。
- lambda 和方法引用没有独立类型,不能单独用
var;lambda 参数用var时必须全体统一。 - 匿名类用
var能调到额外方法,是少数明确收益点。
转载请注明出处,版权归原作者所有。