前言
在编程语言中,变量类型推断并不是一个什么新颖的特性,然而 Java 直到 10 这个版本才有对应的提案去实现。
该提案的实现使得开发者能够使用 var
关键字来简化类型的声明,但目前 var
只能作用于局部变量,如果超出了规定的使用方式,就会出现编译异常。
接下来就一起看看 var
到底能用在哪些地方?不能用在哪些地方?
本文采用的 JDK 版本为 17
正确使用方式
- 最简单也是最常用的使用方式
public void use_normal() {
var intNumber = 1; // int
var longNumber = 1L; // long
var doubleNumber = 1.1d; // double
var str = "hello"; // String
var obj = new Object(); // object
var list = new ArrayList<String>(); // ArrayList<String>
var map = new HashMap<String, String>(); // HashMap<String, String>
}
- 可以和 final 一起配置使用
public void use_with_final() {
final var name = "vran";
name = "vran2"; // compile error: Cannot assign a value to final variable 'name'
}
- 还可以用于 for 循环之中的变量定义
public void use_in_for() {
int[] numbers = {1, 2, 3};
for (var i = 0; i < numbers.length; i++) {
System.out.println(i);
}
}
public void use_in_forEach() {
String[] names = {"tom", "jack"};
for (var name : names) {
System.out.println(name);
}
}
- 用于 Lambda 表达式内
public void use_in_lambda() {
Stream.of("a", "b", "c")
.forEach((var name) -> System.out.println(name));
}
- 用于匿名类初始化
public void use_in_anonymous_class() {
var runnable = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};
}
错误使用方式
var
不是一个保留关键字(keyword),而是一个保留的类名(class name)。这意味着你可以使用var
来作为变量名、方法名、包名,但是不能将其用作类名
package com.demo.var; // 用作包名, ok
class Demo {
public void use_var_as_variable_name() {
Integer var = 1; // 用作变量名, ok
}
public void use_var_as_variable_name2() {
// 可以同时使用 var 作为变量名和类型名, ok
String var = "what is var?";
var name = "ok";
}
public void var() { // 用作方法名, ok
}
}
class var { // compile error: 'var' is a restricted identifier and cannot be used for type declarations
}
- 初始值不能为 null,因为系统无法知道后续该变量会被赋予为什么类型
public void invalid_if_use_to_null_variable() {
var npe = null; // compile error: Cannot infer type: variable initializer is 'null'
}
- 不能引用未初始化的变量
public void invalid_if_use_to_reference_uninitialized_variable() {
Integer number;
var number2 = number; // compile error: Variable 'number' might not have been initialized
}
- 不能用于属性声明(Field)
public class Demo {
var intNumber = 1; // compile error: Cannot resolve symbol 'var'
var longNumber = 1L; // compile error: Cannot resolve symbol 'var'
Demo() {
}
}
- 不能作为方法参数定义
public void invalid_if_use_at_parameter(var number) { // compile error: Cannot resolve symbol 'var'
number++;
}
- 不能用于 catch 的 Exception 变量定义
public void invalid_if_use_at_catch() {
try {
Path path = Paths.get("/temp");
Stream<Path> list = Files.list(path);
} catch (var e) { // compile error: Cannot resolve symbol 'var'
}
}
- 不能用于方法引用
public void invalid_if_use_method_reference() {
var toString = Object::toString; // Cannot infer type: method reference requires an explicit target type
}
- 不能用于初始化数组
public void invalid_if_use_array_initializer() {
int[] numbers = {1, 2, 3};
var names = {"a", "b", "c"}; // compile error: Array initializer is not allowed here
}
推荐实践
使用 var
既可以编写出清晰的代码,也可以编写出混乱的代码,这些都取决于使用者如何使用。
JDK 的开发者 Stuart Marks 编写了一份关于 var
的 style guidelines,感兴趣可以看一下: openjdk.org/projects/am…
我在 Databasir 这个项目里做了一些尝试和探索,结合上面的 style guidelines 总结了 3 条我认为最核心的实践指南
- G1: 变量命名比使用
var
更重要
// 糟糕的命名
List<String> list = new ArrayList<>();
// 虽然通过 var 精简了代码,但是代码的信息量并不比上面少
var customerList = new ArrayList<>();
- G2: 若变量的初始化行为已经提供了足够多的类型信息时,可以优先使用
var
来声明
// 使用 var
var userList = new ArrayList<User>();
var user = UserFactory.create();
var usersGroupByFirstName = new HashMap<String, List<String>>();
// 不使用 var
List<User> userList = new ArrayList<>();
User user = UserFactory.create();
Map<String, List<String>>() usersGroupByFirstName = new HashMap<>();
- G3:若使用 var 的话,尽量最小化局部变量的作用域
下面就是一个反面例子,变量的定义和使用形成了一个作用域范围,这个范围越大,不稳定的因素也会变大
var items = new ArrayList<Item>(...);
// ... 100 lines of code ...
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...
比如将 new ArrayList<Item>
替换成 new HashSet<Item>
,咋一看编译没错,但是代码逻辑却产生了变化,又由于变量的使用举例定义太远,这个错误通常容易被忽视掉。
var items = new HashSet<Item>(...);
// ... 100 lines of code ...
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...
最后
总的来看 var 确实提升了一定的开发幸福感,但对于已经习惯了 Java 这种死板的语法的人来说,var 的使用也让人感受到了一种不确定感。
参考
- Local Variable Type Inference(Style Guidelines), openjdk.org/projects/am…
- Local Variable Type Inference(Frequently Asked Questions), openjdk.org/projects/am…
- JEP 286: Local-Variable Type Inference, openjdk.org/jeps/286
- JEP 323: Local-Variable Syntax for Lambda Parameters, openjdk.org/jeps/323