这是一份如何在 Java 中食用 var 的指南

1,380 阅读4分钟

前言

在编程语言中,变量类型推断并不是一个什么新颖的特性,然而 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 编写了一份关于 varstyle 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 的使用也让人感受到了一种不确定感。

参考