1. 概述

JDK 10 最显著的新特性之一,就是支持局部变量的类型推断(Local-Variable Type Inference),通过引入 var 关键字简化变量声明。

本文将深入解析这一特性,并结合实际示例说明其使用方式、限制以及最佳实践。✅

2. 基本用法与原理

在 Java 9 及之前版本中,声明局部变量必须显式写出类型:

String message = "Good bye, Java 9";
List<String> names = Arrays.asList("Alice", "Bob");

从 Java 10 开始,可以使用 var 让编译器自动推断类型:

@Test
public void whenVarInitWithString_thenGetStringTypeVar() {
    var message = "Hello, Java 10";
    assertTrue(message instanceof String);
}

核心机制

  • var 不是关键字(keyword),而是“保留类型名”(reserved type name),类似 intlong
  • 编译器根据赋值右侧的初始化表达式(initializer)推断出变量的实际类型
  • 推断发生在编译期,无任何运行时开销
  • 变量类型一旦确定,不可更改 —— Java 依然是静态类型语言 ❌ 动态类型

优势

  • 减少样板代码(boilerplate)
  • 提升可读性,聚焦变量名而非冗长类型声明
  • 尤其适用于泛型集合等复杂类型

例如:

// Java 9 及以前
Map<Integer, List<Map<String, Integer>>> data = new HashMap<>();

// Java 10+
var data = new HashMap<Integer, List<Map<String, Integer>>>();

简洁不少,且不影响理解。

⚠️ 重要限制

该特性仅适用于带初始化的局部变量,以下场景不支持:

  • 成员变量(字段)
  • 方法参数
  • 返回类型
  • 没有初始化的变量

3. var 的非法使用场景

以下写法都会导致编译错误 ❌

3.1 缺少初始化表达式

var n; // 编译错误:cannot use 'var' on variable without initializer

3.2 初始化为 null

var emptyList = null; // 编译错误:variable initializer is 'null'

因为 null 无法提供类型信息,编译器无法推断。

3.3 用于非局部变量

public var name = "test"; // 编译错误:'var' is not allowed here

字段、静态变量等均不支持。

3.4 Lambda 表达式(缺少目标类型)

var p = (String s) -> s.length() > 10; // 编译错误:lambda expression needs an explicit target-type

Lambda 需要上下文来确定函数式接口类型,var 无法提供该信息。

✅ 正确写法:

Predicate<String> p = (String s) -> s.length() > 10;

3.5 数组初始化器(Array Initializer)

var arr = { 1, 2, 3 }; // 编译错误:array initializer needs an explicit target-type

❌ 错误原因:这种语法没有显式类型声明,编译器无法推断。

✅ 正确写法:

var arr = new int[]{1, 2, 3}; // ✅ 推断为 int[]

4. 使用建议与避坑指南

虽然 var 合法,但滥用可能导致代码可读性下降。以下是几个典型“踩坑”场景及建议 ✅。

4.1 避免隐藏方法返回类型

var result = obj.prcoess(); // 踩坑!process() 返回什么类型?不清楚

如果方法名不够自解释,使用 var 会让维护者困惑。建议:

  • 若类型明显,可用 var
  • 否则显式声明类型以增强可读性

✅ 改进示例:

Optional<User> result = userService.findUserById(1001);

4.2 流式操作长链慎用

var x = emp.getProjects().stream()
    .findFirst()
    .map(String::length)
    .orElse(0);

这条链最终返回 int,但一眼看去难以判断。使用 var 反而增加了理解成本。

✅ 建议显式声明:

int projectLength = emp.getProjects().stream()
    .findFirst()
    .map(String::length)
    .orElse(0);

变量名 + 明确类型,更清晰。

4.3 注意钻石操作符(Diamond Operator)的推断结果

var empList = new ArrayList<>();

你以为它是 ArrayList<Employee>?错!

⚠️ 实际推断结果是 ArrayList<Object>,因为编译器没有泛型上下文。

✅ 正确做法:

var empList = new ArrayList<Employee>(); // 显式指定泛型

或者更推荐:

var empList = new ArrayList<String>(); // 明确类型

4.4 匿名类实例的陷阱

@Test
public void whenVarInitWithAnonymous_thenGetAnonymousType() {
    var obj = new Object() {};
    assertFalse(obj.getClass().equals(Object.class));
}

这里 obj 的类型不是 Object,而是编译器生成的匿名子类类型(如 Object$1)。

这意味着你不能再赋值一个普通 Object

obj = new Object(); // ❌ 编译错误:Object cannot be converted to <anonymous Object>

⚠️ 原因:var 推断出的是匿名类类型,而非父类。

✅ 建议:这种场景避免使用 var,显式声明更安全:

Object obj = new Object() {}; // 类型为 Object,可后续赋值

5. 总结

Java 10 引入的 var 是一个简单粗暴但非常实用的语法糖,能有效减少冗余代码,提升开发效率 ✅。

但记住:

  • 它是编译期推断,不是动态类型
  • 仅适用于带初始化的局部变量
  • 能用 ≠ 应该用,可读性优先

合理使用 var,能让代码更简洁;滥用则可能埋下维护隐患。

📚 官方风格指南参考:OpenJDK - Local Variable Type Inference Style Guide

💡 示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-10


原始标题:Java 10 LocalVariable Type-Inference