将var限定在局部变量上并非技术方面的局限,而是设计上的决定。确实,如果能够像下面这样岂不更好?
// 编译器推断出类型List<User>
var users = new ArrayList<User>();
// 这样就不行了,会出现编译错误
users = new LinkedList<>();
按照预期,编译器应该能够找出最具体的那个类型,但实际上它不会。JDK团队想要避免“Action At A Distance”错误(AAD),也就是说,他们希望在某处修改了代码不会影响到其他很“远”的地方。比如:
// id被推推为`int`
var id = 123;
if (id < 100) {
// 此处省略了很长的代码
// 调用了其他类的方法
} else {
// 此处也省略了很长的代码
}
现在,我们加入一行:
id = "124"
这样会发生什么?if代码块会抛出一个错误,因为id变成了字符串类型,所有不能使用小于号进行比较操作。这个错误距离代码修改的地方很“远”,而其根源就是因为对一个变量重新赋值。
这么看来,将类型推断限定在带有构造器的局部变量声明上是有它的道理的。
为什么不推断字段和方法的类型?
字段和方法的作用域比局部变量大得多,所以更有可能出现AAD错误。在最糟糕的情况下,修改一个方法的参数类型可能导致二进制文件的不兼容和运行时错误。
因为非private的字段和方法是类契约的一部分,它们的类型不能通过推断来获得。不过,private的字段和方法似乎可以使用类型推断,但问题是这样会让这个特性看起来非常奇怪。
局部变量属于实现细节,通常不会在很“远”的地方引用这些变量,所以就没有必要严格、显式和啰嗦地给它们声明类型了。
为什么要使用var?
相比其他年轻的编程语言,Java代码的啰嗦是开发人员最大的痛点之一,也是饱受Java开发人员诟病的一个地方。为此,Amber项目开发了var,旨在“开发出一些小的Java语言特性,以便提高效率”,其目标是降低Java代码编写和阅读的繁琐程度。
局部变量类型推断正好迎合了这一目标。在编写代码时,声明变量的方式更简单了,尽管这类代码有大半可以使用IDE生成,或者使用IDE的重构功能进行修改。
除了让变量声明变得更简单,修改起来也很容易。这话怎么说?有些变量的类型真的很难看,比如那些带有泛型的企业级类名:
InternationalCustomerOrderProcessor<AnonymousCustomer, SimpleOrder<Book>> orderProcessor = createInternationalOrderProcessor(customer, order);
因为类型名称太长了,结果把变量名推到了代码的右边。如果限定了每行只能容纳150个字符,那么变量名还有可能被推到下一行显示。这些对于可读性来说都是一种伤害。
var orderProcessor = createInternationalOrderProcessor(customer, order);
使用var就显得不那么累赘了,一眼就能看到头。
总之,使用var的意义不在于减少字符数量,而是为了不那么啰嗦和累赘。
对可读性的影响
现在让我们来讲讲可读性。确实,类型的缺失会让事情变得更糟糕,不是吗?一般来说,确实是的。在阅读代码时,类型是很重要的一个因素。尽管IDE可以帮助显示出推断的类型,但如果这些类型直接显示在代码中看起来不是更方便吗?
这是var在可读性方面的一个不足,不过,它却带来了另一个优势,那就是变量名对齐:
// 显式类型
No no = new No();
AmountIncrease<BigDecimal> more = new BigDecimalAmountIncrease();
HorizontalConnection<LinePosition, LinePosition> jumping =
new HorizontalLinePositionConnection();
Variable variable = new Constant(5);
List<String> names = List.of("Max", "Maria");
// 推断类型
var no = new No();
var more = new BigDecimalAmountIncrease();
var jumping = new HorizontalLinePositionConnection();
var variable = new Constant(5);
var names = List.of("Max", "Maria");
虽说类型名称很重要,但好的变量名也是有过之而无不及。类型用于描述Java生态系统(JDK的类)、一般场景(类库或框架)或业务领域(应用程序)的一般性概念,所以类型一般会有通用的名字。而变量名处在很小的上下文中,它们的名字应该要更精确一些。
这种可读性方面的改进可能会导致出现更多带有构造器的局部变量声明,因为这样在编写代码和阅读代码时会更加方便。
为什么没有使用val/const/let?
其他很多使用了var的编程语言也会为不可变变量提供一个额外的关键字,比如val、const或let。但Java 10没有使用这些关键字,所以我们必须使用final var来声明不可变变量。究其原因,可能是因为: