Java 10引入的局部变量类型推断让var关键字在赋值语句中频繁出现,但它同样可以直接用于增强for循环的迭代变量声明。这个用法看似简单,却能让那些充斥着嵌套泛型的遍历代码清爽不少。不过,var在循环头部并不是简单的文本替换,编译器会根据集合或数组的元素类型进行静态推断,因此理解其推断规则和适用边界,才能既享受简洁又不损失可读性。

增强for循环的传统写法与痛点
增强for循环从Java 5开始引入,用来替代迭代器遍历集合或数组,语法比传统for循环更简洁。标准写法是在冒号前声明迭代变量的类型和名称,例如遍历一个字符串列表时写成for (String item : list)。当集合元素类型是简单类名时,这样的写法并不麻烦,甚至因为类型一目了然,可读性很好。
但实际项目中集合元素经常是复杂的泛型结构。比如一个订单列表可能是List<Map<String, List<OrderItem>>>,此时迭代变量如果要完整写出类型,行长度会迅速膨胀。即便使用IDE自动补全,阅读代码的人仍然需要在大脑里解析这一长串泛型,才能确定每次循环拿到的到底是什么对象。更糟糕的是,如果后续重构改变了集合的泛型参数,所有相关的增强for循环头部都需要同步修改,维护成本上升。传统写法在这种情况下显得格外笨重。
// 传统增强for循环
List<Map<String, List<OrderItem>>> orderMaps = getOrderMaps();
for (Map<String, List<OrderItem>> orderMap : orderMaps) {
// 处理每个订单Map
}
这段代码中,迭代变量的类型声明占据了很大篇幅,真正重要的循环体逻辑反而被挤到后面。如果集合类型再深一层,几乎无法在一行内保持清晰。因此,需要一种方式在不丢失静态类型安全的前提下,减少重复的类型书写。
var关键字如何简化迭代变量声明
从Java 10开始,局部变量类型推断扩展到增强for循环的迭代变量上。语法形式为for (var item : collection)。编译器会查看集合或数组的声明类型,自动确定item的具体类型。对于泛型集合,var推断出的类型是元素的实际泛型参数;对于数组,推断出的类型是数组的组件类型。这种推断发生在编译期,生成的字节码与显式声明类型完全一致,因此不会带来任何运行时开销。
以上面的订单列表为例,使用var后代码会变成:
// 使用var简化迭代变量
List<Map<String, List<OrderItem>>> orderMaps = getOrderMaps();
for (var orderMap : orderMaps) {
// orderMap的类型被推断为Map<String, List<OrderItem>>
List<OrderItem> items = orderMap.get("pending");
}
可以看到,原本需要反复书写的长泛型类型被var取代,循环头部只关注变量名。这一特性对于遍历Map的entrySet尤其方便,因为Map.Entry<K, V>的完整类型写起来往往比实际业务代码更长。需要注意的是,var推断的是集合元素的精确静态类型,而不是变量的实际运行时类型。例如声明为List<String>的变量,无论实际对象是ArrayList还是LinkedList,var推断出的迭代变量类型都是String。但如果集合是原始类型List而没有泛型参数,var会推断为Object,使用时需要手动转型,这种情况下显式声明反而更好。
此外,var在增强for循环中不能与final等修饰符组合使用,但迭代变量本身在Java中默认不允许重新赋值,因此var并不会改变这一语义。
适用场景与边界
var在增强for循环中并非万能。它最适合的场景是集合元素类型非常明确且变量名能够传递足够信息的情况。例如遍历List<Customer>时写成for (var customer : customers),代码读者从变量名customer就能知道类型,此时用var几乎没有信息损失。又如遍历Map<String, List<Transaction>>的entrySet时,完整类型太长,用var entry配合后续的entry.getKey()、entry.getValue()调用,可读性反而提升。
但也有不适合使用var的情况。第一种是当迭代变量需要以父类或接口类型使用时。例如一个List<ArrayList<String>>,如果代码里希望把每个元素当作List<String>来处理,显式声明for (List<String> list : lists)会更合适;使用var会被推断为ArrayList<String>,虽然通常也能用,但丢失了面向接口编程的灵活性。第二种是集合元素类型不够直观,或者变量名过于简短导致类型难以推断时,例如for (var d : dataList),开发者很难知道d是什么,这时显式写出类型有助于理解。第三种是当代码需要兼容Java 9及以下版本时,var根本不可用,团队应根据实际运行环境决定是否采用。
实践中建议把var当作一种减少噪音的工具,而不是替代所有类型声明。团队可以约定:当元素类型的完整书写超过一定长度,或者变量的名称已经足够表达类型含义时使用var;对于基础类型和常见业务实体,保留显式类型更符合大多数人的阅读习惯。
几个典型代码示例与最佳实践
下面通过几个例子展示var在增强for循环中的常见用法。第一个例子遍历Map,这是var最能发挥价值的场景之一:
Map<String, List<User>> userGroups = getUserGroups();
for (var entry : userGroups.entrySet()) {
String groupName = entry.getKey();
List<User> users = entry.getValue();
for (var user : users) {
System.out.println(user.getName());
}
}
第二个例子遍历数组。数组的元素类型同样会被正确推断,包括基本类型数组。比如int[]使用增强for循环时,var推断出的是int而不是包装类型Integer:
int[] numbers = {1, 2, 3, 4, 5};
for (var num : numbers) {
// num是基本类型int
System.out.println(num * 2);
}
第三个例子展示原始类型集合的坑。如果集合声明为原始类型List,没有泛型参数,var推断出的迭代变量类型是Object,直接使用会编译失败,需要手动转型:
List rawList = getRawList();
for (var obj : rawList) {
// obj的类型是Object,无法直接调用具体方法
if (obj instanceof String) {
String s = (String) obj;
}
}
最佳实践方面,首先要保证变量名具有明确的业务含义,让var不会造成理解障碍。其次,在循环体内部尽量只使用迭代变量的基础操作,避免依赖其具体实现类。第三,如果发现用var后自己需要频繁查看集合声明才能确定类型,说明这个场景不适合var,应当改回显式类型。最后,var只是编译期语法糖,不会影响性能,也不会改变泛型的类型安全特性,开发者可以放心在合适的场景中使用。