Java 10 最显眼的变化只有一个关键字:var。其余改进集中在 JVM 底层,对普通业务代码几乎透明。

版本定位

Java 10 发布于 2018 年 3 月,是半年一发的快节奏调整后的首个版本,非 LTS,官方支持只有六个月。它的历史意义大于实用意义:var 让 Java 冗长的类型声明第一次有了简化手段,G1 并行 Full GC 等底层改动则为后续版本的性能演进铺路。多数企业直接跳过它,从 8 一步升到 11。

局部变量类型推断 var

var 让编译器根据右侧表达式推断局部变量的类型。写法上像 JavaScript,本质却完全不同——Java 依然是静态类型语言,类型在编译期就已固定,推断结果与上下文矛盾照样编译失败:

var list = new ArrayList<String>();  // 推断为 ArrayList<String>
var count = 0; // 推断为 int
for (var item : list) { // 增强 for 同样可用
System.out.println(item);
}

适用边界

var 的限制比直觉中多:

  • 只能用于局部变量,包括 for 循环变量和 try-with-resources 的资源变量;字段、方法参数、返回值一律不行;
  • 声明时必须初始化,var x; 直接编译报错;
  • 初始值不能是 null,编译器无从推断;
  • 右侧不能是 Lambda 或方法引用这类缺少目标类型的表达式,var f = () -> 1; 不成立;
  • var list = new ArrayList<>() 会把钻石操作符推断成 ArrayList<Object>,与泛型推断混用时尤其要小心。

可读性之争

var 从诞生起就伴随争论。反对者认为代码被阅读的次数远多于被编写的次数,省掉的类型恰恰是最重要的信息;支持者则反问 Map<String, List<BigDecimal>> map = new HashMap<>() 这种重复声明除了噪音还有什么。

实践中比较公认的取舍是:右侧一眼能看出类型时用 var,例如构造器调用、知名工厂方法;右侧是泛型方法调用、反射结果或复杂三元表达式时保留显式类型。IDE 虽然能悬浮显示推断类型,但代码评审常常发生在没有 IDE 辅助的 diff 视图里,团队最好在编码规范中提前写明约定。

顺手的 API 小增强

语言之外,Java 10 的标准库也有两处实用补充:

  • 不可变拷贝:List、Set、Map 新增静态方法 copyOf(),返回入参集合的不可变拷贝;Collectors 同步补充了 toUnmodifiableList()、toUnmodifiableSet() 等收集器,把 Stream 结果直接收进不可变容器。和 Java 9 的 of() 一样,这些集合拒绝 null 元素,修改操作会抛异常。
  • Optional.orElseThrow() 无参版:不再需要手动传异常工厂,无值时默认抛出 NoSuchElementException,语义比 get() 更清晰,建议全面替代后者。

底层改进

Java 10 的其余变化基本发生在 JVM 内部,日常编码感知不到,但值得定性了解:

  • G1 并行 Full GC:Java 9 起 G1 成为默认回收器,但最坏情况下的 Full GC 仍由单线程完成标记-清除-压缩,停顿可能长达数秒。Java 10 把这一过程并行化,显著缩短极端场景的停顿时间。注意它缓解的是「Full GC 发生时有多痛」,并不减少触发次数,堆调优的思路不变。
  • GC 接口化:各回收器共用的基础设施被抽象成干净的内部接口,HotSpot 的 GC 代码更模块化,为后续 ZGC、Shenandoah 的接入降低了成本,对使用者完全透明。
  • AppCDS 开放:应用程序类数据共享由此前的商业特性转为开放,可以把应用类预处理后放入共享归档,加快启动并降低同机多实例的内存占用。

升级建议

没有任何理由停留在 Java 10:支持期早已结束,全部特性在 Java 11 中原样保留。它留给今天的主要价值是 var 的边界与规范——如果团队正准备从 8 升级,不妨顺手把「何时用 var」写进编码规范,避免升级后各自为战。

小结

Java 10 把 var 交给了开发者,同时把 G1 的 Full GC 从单线程改为并行。前者改变了写代码的手感,并引出一场持续至今的可读性争论;后者是 JVM 持续压低停顿的其中一步。非 LTS 的定位决定了它更适合被理解,而不是被部署。