var 是 Java 10(JDK 10,2018年,对应 JSR 286)正式引入的语法特性,全称为局部变量类型推断(Local Variable Type Inference,LVTI),Java 11 进一步扩展了它在 Lambda 表达式中的使用能力。它的核心价值是简化局部变量的声明代码,由编译器自动推导变量的静态类型,同时完全保留 Java 静态强类型语言的本质。


一、核心本质:编译期语法糖

var 是典型的编译期语法糖,不涉及任何运行时增强:

  1. 编译期推断类型:javac 编译器会根据变量右侧的初始化表达式,自动推导变量的具体静态类型,并写入最终的字节码文件。
  2. 运行时无差异:编译生成的字节码,和显式书写完整类型的代码完全一致,没有任何额外的运行时开销,也不存在动态类型特性。
  3. 强类型约束不变:变量类型一旦在编译期确定,就无法再赋值其他类型的值,完全遵守 Java 的强类型规则。

简单来说:var 只是帮你省略了手写类型的步骤,Java 仍然是严格的静态强类型语言,和 JavaScript 等动态语言的 var 没有任何共性。


二、合法使用场景

var 的使用范围被严格限制在局部变量范畴,具体支持以下场景:

1. 方法体内的局部变量声明

必须在声明时同时完成初始化,编译器通过初始化表达式推断类型。

// 基本类型
var num = 10;          // 推断为 int
var price = 19.99;     // 推断为 double

// 引用类型
var name = "hello";    // 推断为 String
var user = new User(); // 推断为 User

// 复杂泛型(最能体现简化价值的场景)
var userMap = new HashMap<String, List<User>>(); // 推断为 HashMap<String, List<User>>

2. for 循环中的循环变量

包括普通 for 循环和增强 for 循环:

// 普通for循环
for (var i = 0; i < 10; i++) { // i 推断为 int
    System.out.println(i);
}

// 增强for循环
var list = List.of("a", "b", "c");
for (var item : list) { // item 推断为 String
    System.out.println(item);
}

3. try-with-resources 资源变量

简化资源声明代码,尤其适合类型冗长的流、连接对象:

try (var reader = new BufferedReader(new FileReader("data.txt"))) {
    // reader 推断为 BufferedReader
    String line = reader.readLine();
} catch (IOException e) {
    e.printStackTrace();
}

4. (JDK 11+)Lambda 表达式参数

Java 11 扩展了 var 的能力,允许在 Lambda 表达式的参数上使用,核心价值是在需要给参数加注解时,无需书写完整类型名

// 无注解场景,和直接写参数名效果一致
UnaryOperator<String> trim = (var s) -> s.trim();

// 带注解场景,简化代码(不加var则必须写全参数类型)
UnaryOperator<String> trim = (@NonNull var s) -> s.trim();

三、禁止使用的场景

var 的设计非常克制,以下场景均不允许使用,编译会直接报错:

1. 类的成员变量(字段)

成员变量的初始化时机多样(构造方法、代码块、setter等),编译器无法可靠地统一推断类型,因此不支持。

public class User {
    private var name; // 编译错误:字段不能使用var
}

2. 方法的形参、返回值类型

方法签名是对外的契约,必须显式明确类型,不能依赖推断。

// 编译错误:参数、返回值均不能使用var
public var getName(var id) {
    return "test";
}

3. 无初始化的变量声明

没有初始化表达式,编译器无法推断类型。

var a; // 编译错误:缺少初始化器

4. 仅赋值为 null 的变量

null 本身没有具体类型,无法作为推断依据;但如果对 null 做了强制类型转换,则可以推断。

var a = null;          // 编译错误:无法从null推断类型
var b = (String) null; // 编译通过,推断为String

5. 一行声明多个变量

var a = 1, b = 2; // 编译错误:不支持多变量同时声明

6. 简写数组初始化器

简写的花括号数组初始化,需要依赖左侧类型反向推断,与 var 的推断方向冲突,因此不支持;显式 new 数组则可以。

var arr1 = {1, 2, 3};       // 编译错误:不支持简写数组初始化
var arr2 = new int[]{1,2,3};// 编译通过,推断为 int[]

7. 作为类型名称

var 是 Java 的保留类型名,不能用来命名类、接口、枚举、注解等类型,但可以作为变量名、方法名(极度不推荐)。

public class var {} // 编译错误:不能用var作为类名

四、关键特性与易踩坑点

1. 推断的是「编译时静态类型」,而非接口类型

var 会严格按照初始化表达式的编译时类型进行推断,不会自动向上转型为接口/父类类型,这是最容易踩的坑。

// 推断结果为 ArrayList<String>,而不是 List<String>
var list = new ArrayList<String>();

// 推断结果为 Dog,而不是 Animal
var animal = new Dog();

如果希望面向接口编程(使用父类/接口类型),需要显式声明变量类型,不能使用 var

2. 配合钻石运算符 <> 的注意事项

如果右侧使用钻石运算符且没有指定泛型类型,var 会将泛型参数推断为 Object,而非自动推导具体类型。

// 推断为 ArrayList<Object>,而非泛型原始类型
var list = new ArrayList<>();

// 推荐写法:右侧显式指定泛型,保证推断准确
var list = new ArrayList<String>();

3. 可配合修饰符使用

var 只是推断类型,完全兼容局部变量的修饰符,比如 final

final var MAX_SIZE = 100; // 编译通过,等价于 final int MAX_SIZE = 100

五、最佳实践与使用建议

推荐使用的场景

  1. 复杂泛型嵌套:右侧类型明确且冗长时,用 var 显著提升代码简洁度。
  2. 短作用域临时变量:流式编程、链式调用的中间变量,作用域仅几行,类型一目了然。
  3. try-with-resources:简化资源对象的声明。
  4. 循环变量:类型明确且无歧义的循环场景。

不推荐使用的场景

  1. 初始化表达式类型不直观:比如方法返回值赋值,仅看代码无法一眼判断返回类型时,会降低可读性。
  2. 长作用域变量:变量跨越多行代码,读者无法快速看到初始化表达式时,var 会增加理解成本。
  3. 数值类型易歧义:比如 var count = 0L,容易被误认为是 int,此时建议显式声明类型。
  4. 需要面向接口抽象:希望变量使用接口/父类类型、隐藏实现类时,必须显式声明。

核心原则:使用 var 的前提是,读者能从代码上下文中一眼看出变量的类型,且不会产生歧义。


六、常见误区澄清

  1. 误区:var 是动态类型
    纠正:编译期就已确定变量类型,运行时不可修改,完全遵守 Java 强类型规则,和动态语言无关。

  2. 误区:var 会带来性能损耗
    纠正:纯编译期语法糖,字节码和显式写类型完全一致,运行时没有任何额外开销。

  3. 误区:var 只能用于基本类型
    纠正:支持所有 Java 类型,包括引用类型、自定义类、泛型、数组等。

  4. 误区:var 是 Java 的保留关键字
    纠正:var 是「保留类型名」,不是严格的保留关键字——不能用作类名,但可以用作变量名(规范上强烈不推荐)。

需要我补充一份 var 正确与错误用法的代码对比清单吗?

更多推荐