JDK11 | 第三篇 : 局部变量类型推断

栏目: Java · 发布时间: 6年前

内容简介:Java局部变量类型推断(LVTI),简称var类型(标识符var不是一个关键字,是一个预留类型名),Java10中新添加的功能。作为100%编译特征,通常我们在起全局变量名的时候会注意这一点,但是选择局部变量名的时候不太注意。尤其是当方法很短,方法名和实现都不错的时候,我们趋向于简化我们的变量名。但是当我们使用var替代显式类型的时候,具体的类型是通过编译器推断出来的。所以,对于人来说阅读或者理解代码非常困难。在这一点上var削弱了代码可读性。这种事情之所以会发生,是因为大多数情况下,我们会把变量类型当成

Java局部变量类型推断(LVTI),简称var类型(标识符var不是一个关键字,是一个预留类型名),Java10中新添加的功能。作为100%编译特征, 它不会影响字节码,运行时或者性能 。在编译时,编译器会检查赋值语句右侧代码,从而推断出具体类型。它查看声明的右侧,如果这是一个初始化语句,它会用那个类型取代var。另外,它非常有助于减少冗余代码和样板代码。

二、使用及注意事项

1. 争取起有意义的局部变量名

通常我们在起全局变量名的时候会注意这一点,但是选择局部变量名的时候不太注意。尤其是当方法很短,方法名和实现都不错的时候,我们趋向于简化我们的变量名。但是当我们使用var替代显式类型的时候,具体的类型是通过编译器推断出来的。所以,对于人来说阅读或者理解代码非常困难。在这一点上var削弱了代码可读性。这种事情之所以会发生,是因为大多数情况下,我们会把变量类型当成是第一信息,而把变量名当成第二信息。但是使用var的时候,恰恰相反。

示例1

即使到这里,一些朋友仍然坚持局部变量名短点好。我们看一下:

// HAVING
public boolean callDocumentationTask() {
    DocumentationTool dtl = ToolProvider.getSystemDocumentationTool();
    DocumentationTask dtt = dtl.getTask(...);
    return dtt.call();
}
复制代码

我们换成var时,避免:

public boolean callDocumentationTask() {
    var dtl = ToolProvider.getSystemDocumentationTool();
    var dtt = dtl.getTask(...);
    return dtt.call();
}
复制代码

更好:

public boolean callDocumentationTask() {
    var documentationTool = ToolProvider.getSystemDocumentationTool();
    var documentationTask = documentationTool.getTask(...);
  return documentationTask.call();
}
复制代码

示例2

避免:

public List<Product> fetchProducts(long userId) {
    var u = userRepository.findById(userId);
    var p = u.getCart();
    return p;
}
复制代码

更好:

public List<Product> fetchProducts(long userId) {
    var user = userRepository.findById(userId);
    var productList = user.getCart();
    return productList;
}
复制代码

示例3

争取为局部变量起有意义的名字并不意味着要掉入过度命名的坑,避免在短方法中使用单一类型的数据流:

var byteArrayOutputStream = new ByteArrayOutputStream();
复制代码

用如下代码代替更加清晰:

var outputStream = new ByteArrayOutputStream();
// or
var outputStreamOfFoo = new ByteArrayOutputStream();
复制代码

2. 使用数据类型标志来帮助var去推断出预期的基本数据类型(int, long, float, double)

如果在基本数据类型中不使用有效的数据类型标志,我们可能会发现预期的类型和推测出的类型不一致。这是由于var的隐式类型转换导致的。

例如,下面两行代码的表现是符合预期的,首先,我们声明一个boolean 和一个char使用显式类型:

boolean flag = true; // 这是一个boolean类型
char a = 'a';        // 这是一个char类型
复制代码

现在,我们使用var代替显式基本类型:

var flag = true; // 被推断为boolean类型
var a = 'a';     // 被推断为char类型
复制代码

到目前为止,一切都很完美。接下来,我们看一下相同逻辑下的int, long, double 和 float:

int intNumber = 20;       // 这是int类型
long longNumber = 20;     // 这是long类型
float floatNumber = 20;   // 这是float类型, 20.0
double doubleNumber = 20; // 这是double类型, 20.0
复制代码

以上代码是很常见而且清晰的,现在我们使用var:

避免:

var intNumber = 20;    // 推断为int
var longNumber = 20;   // 推断为int
var floatNumber = 20;  // 推断为int
var doubleNumber = 20; // 推断为int
复制代码

更好:

var intNumber = 20;     // 推断为int
var longNumber = 20L;   // 推断为long
var floatNumber = 20F;  // 推断为float, 20.0
var doubleNumber = 20D; // 推断为double, 20.0
复制代码

但是如果我们使用小数声明一个数字,会发生什么呢?当你认为你的数字是一个float的时候,避免这样做:

// 避免,如果这是一个float
var floatNumber = 20.5; // 推断为double
复制代码

你应该用对应的数据类型标志来避免这样的问题:

// 更好, 如果这是一个float
var floatNumber = 20.5F; // 推断为float
复制代码

3. 在某些情况下,Var和隐式类型转换可以维持可维护性

在某些情况下,Var和隐式类型转换可以维持可维护性。例如,假设我们的代码包含两个方法:第一个方法接收一个包含不同条目的购物卡,比较市场中不同的价格,计算出最好的价格,并汇总返回float类型的总价。另一个方法简单的把这个float价格从卡中扣除。

首先,我们看一下计算最好价格的方法:

public float computeBestPrice(String[] items) {
   ...
   float price = ...;
   return price;
}
复制代码

然后,我们看一下扣款的方法:

public boolean debitCard(float amount, ...) {
    ...
}
复制代码

现在,我们把这两个方法汇总,提供一个服务方法。顾客选择要买的商品,计算最优价格,然后扣款:

//避免
public void purchaseCart(long customerId) {
    ...
    float price = computeBestPrice(...);
    debitCard(price, ...);
}
复制代码

一段时间后,公司想要去除价格中的小数部分作为打折策略,使用int代替了float, 我们需要修改代码:

public int computeBestPrice(String[] items) {
   ...
   float realprice = ...;
   ...
   int price = (int) realprice;
   return price;
}
public boolean debitCard(int amount, ...) {
    ...
}
复制代码

问题在于我们使用了显示类型float,这样的更改不能被兼容。代码会报编译时错误。但是如果我们预判到这种情况,使用var代替float, 我们的代码会因为隐式类型转换而变得没有兼容性问题。

//更好
public void purchaseCart(long customerId) {
    ...
    var price = computeBestPrice(...);
    debitCard(price, ...);
}
复制代码

4. 当数据类型标志解决不了问题的时候,依赖显式向下转换或者避免var

一些 Java 基础数据类型不支持数据类型标志。例如byte和short。使用显式基础数据类型时没有任何问题。使用var代替的时候:

// 这样更好,而不是使用var
byte byteNumber = 45;     // 这是byte类型
short shortNumber = 4533; // 这是short类型
复制代码

为什么在这种情况下显式类型比var好呢?我们切换到var.注意示例中都会被推断为int, 而不是我们预期的类型。

避免使用以下代码:

var byteNumber = 45;    // 推断为int
var shortNumber = 4533; // 推断为int
复制代码

这里没有基础数据类型帮助我们,因此我们需要依赖显示强制类型转换。从个人角度来讲,我会避免这么用,因为没啥好处,但是可以这么用。

如果你真的想用var,这么用:

var byteNumber = (byte) 45;     // 推断为byte
var shortNumber = (short) 4533; // 推断为short
复制代码

5. 如果变量名没有对人来说足够的类型信息,避免使用var

使用var有助于提供更加简练的代码。例如, 在使用构造方法时(这是使用局部变量的常见示例),我们可以简单地避免重复类名的必要性,从而消除冗余。

避免:

MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...);
复制代码

更好:

var inputStream = new MemoryCacheImageInputStream(...);
复制代码

在下面的结构中,var也是一个简化代码而不丢失信息的好方法。

避免:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(...);
复制代码

更好:

var compiler = ToolProvider.getSystemJavaCompiler();
var fileManager = compiler.getStandardFileManager(...);
复制代码

为什么这样基于var的例子我们感觉比较舒服呢?因为需要的信息已经在变量名中了。但是如果使用var 加上变量名,还是会丢失信息,那么最好避免使用var。

避免:

public File fetchCartContent() {
    return new File(...);
}
// As a human, is hard to infer the "cart" type without 
// inspecting the fetchCartContent() method
var cart = fetchCartContent();
复制代码

使用以下代码代替:

public File fetchCartContent() {
    return new File(...);
}
File cart = fetchCartContent();
复制代码

思考一个基于java.nio.channels.Selector的例子。这个类有一个静态方法叫做open(),返回一个新的Selector实例并且执行open动作。但是Selector.open()很容易被认为返回一个boolean标识打开当前选择器是否成功,或者返回void。使用var导致丢失信息会引发这样的困扰。

6. var类型确保编译时安全

var类型是编译时安全的。这意味着如果我们试图实现一个错的赋值,会导致编译时报错。例如,以下代码编译不会通过。

// 编译通不过
var items = 10;
items = "10 items"; // 不兼容类型: String不能转为int
复制代码

以下代码编译会通过:

var items = 10;
items = 20;
复制代码

这个代码也会编译通过:

var items = "10";
items = "10 items" ;
复制代码

所以,一旦编译器已经推断出了var对应的类型,我们只能赋值对应类型的值给它。

7. var 不能被用于将真实类型的实例赋值给接口类型变量

在Java中,我们使用“面向接口编程”的技术。

例如,我们创建一个ArrayList的实例,如下(绑定代码到抽象):

List<String> products = new ArrayList<>();
复制代码

我们避免这样的事情(绑定代码到实现):

ArrayList<String> products = new ArrayList<>();
复制代码

所以,通过第一个例子创建一个ArrayList实例更好,但是我们也需要声明一个List类型的变量。因为List是一个接口,我们可以很容易的切换到List的其他实现类,而无需额外的修改。

这就是“面向接口编程”,但是var不能这么用。这意味着当我们使用var时,推断出的类型是实现类的类型。例如,下面这行代码,推测出的类型是ArrayList:

var productList = new ArrayList<String>(); // 推断为ArrayList<String>
复制代码

以下几个论点支持这一行为:

  • 首先,var是局部变量,大多数情况下,“面向接口编程”在方法参数和返回类型的时候更有用。
  • 局部变量的作用域比较小,切换实现引起的发现和修复成本比较低。
  • var将其右侧的代码视为用于对端实际类型的初始化程序,如果将来修改初始化程序,则推断类型会改变,从而导致后续依赖此变量的代码产生问题。

8. 意外推断类型的可能性

如果不存在推断类型所需的信息,则与菱形运算符组合的var类型可能导致意外推断类型。

在Java 7之前的Coin项目中,我们写了这样的代码:

//显式指定泛型类的实例化参数类型
List<String> products = new ArrayList<String>();
复制代码

从Java 7开始,我们有了菱形运算符,它能够推断泛型类实例化参数类型:

// inferring generic class's instantiation parameter type 
List<String> products = new ArrayList<>();
复制代码

那么,以下代码推断出什么类型呢?

首先应该避免这么用:

var productList = new ArrayList<>(); // 推断为ArrayList<Object>
复制代码

推断出的类型是Object的ArrayList。之所以会这样是因为没有找到能够推测到预期类型为String的信息,这会导致返回一个最广泛可用类型,Object。

所以为了避免这样的情形,我们必须提供能够推断到预测类型的信息。这个可以直接给也可以间接给。

更好的实现(直接):

var productList = new ArrayList<String>(); // 推断为ArrayList<String>
复制代码

更好的实现(间接):

var productStack = new ArrayDeque<String>(); 
var productList = new ArrayList<>(productStack); // 推断为ArrayList<String>
复制代码

更好的实现(间接):

Product p1 = new Product();
Product p2 = new Product();
var listOfProduct = List.of(p1, p2); // 推断为List<Product>
// 不要这么干
var listofProduct = List.of(); // 推断为List<Object>
listofProduct.add(p1); //报错
listofProduct.add(p2); //报错
复制代码

9. 赋值数组到var不需要中括号[]

我们都知道Java中如何声明一个数组:

int[] numbers = new int[5];
// 或者,这样写不太好
int numbers[] = new int[5];
复制代码

那么怎么用var呢?左边不需要使用括号。

避免这么写(编译不通过):

// 编译通不过
var[] numbers = new int[5];
// 或者
var numbers[] = new int[5];
复制代码

应该这么用:

var numbers = new int[5]; // 推断为int数组
numbers[0] = 2;   // 对
numbers[0] = 2.2; // 错
numbers[0] = "2"; // 错
复制代码

另外,这么用也不能编译,这是因为右边没有自己的类型:

// 显式类型表现符合预期
int[] numbers = {1, 2, 3};
// 编译通不过
var numbers = {1, 2, 3};
var numbers[] = {1, 2, 3};
var[] numbers = {1, 2, 3};
复制代码

10. var类型不能被用于复合声明(一行声明多个变量)

如果你喜欢复合声明,你一定要知道var不支持这种声明。下面的代码不能编译:

// 编译通不过
// error: 'var' 不允许复合声明
var hello = "hello", bye = "bye", welcome = "welcome";
复制代码

用下面的代码代替:

String hello = "hello", bye = "bye", welcome = "welcome";
复制代码

或者这么用:

var hello = "hello";
var bye = "bye";
var welcome = "welcome";
复制代码

11. 局部变量应力求最小化其范围。var类型强化了这一论点

局部变量应该保持小作用域,我确定你在var出现之前就听过这个,这样可以增强代码可读性,也方便更快的修复bug。

例如我们定义一个java栈:

避免:

...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// 50行不用stack的代码
// George, Tyllen, Martin, Kelly  
stack.forEach(...);
...
复制代码

这不是我们想要的,我们很难看出引入了一个错误,因为包含forEach()部分的代码不在研发完成修改的代码附近。为了快速修复这个错误,并避免上下滚动来了解发生了什么,最好缩小stack变量的作用域范围。

最好这么写:

...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// George, Tyllen, Martin, Kelly  
stack.forEach(...);
...
// 50行不用stack的代码
复制代码

现在,当开发人员从Stack切换到ArrayQueue的时候,他们能够很快的注意到bug,并修复它。

12. var类型便于三元运算符右侧的不同类型的操作数

我们可以在三元运算符的右侧使用不同类型的操作数。

使用具体类型的时候,以下代码无法编译:

// 编译通不过
List code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// or
Set code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
复制代码

虽然我们可以这么写:

Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
复制代码

这样也编译不过:

// 编译通不过:
int code = intOrString ? 12112 : "12112";
String code = intOrString ? 12112 : "12112";
复制代码

但是我们可以这么写:

Serializable code = intOrString ? 12112 : "12112";
Object code = intOrString ? 12112 : "12112";
复制代码

在这种情况下,使用var更好:

// inferred as Collection<Integer>
var code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// inferred as Serializable
var code = intOrString ? 12112 : "12112";
复制代码

千万不要从这些例子中得出var类型是在运行时做类型推断的,它不是!!!

当然,我们使用相同的类型作为操作数时var是支持的。

// 推断为float
var code = oneOrTwoDigits ? 1211.2f : 1211.25f;
复制代码

13. var类型能够用在循环体中

我们能非常简单的在for循环中用var类型取代具体类型。这是两个例子。

var替换int:

// 显式类型
for (int i = 0; i < 5; i++) {
     ...
}
// 使用 var
for (var i = 0; i < 5; i++) { // i 推断为 int类型
     ...
}
复制代码

var替换Order:

List<Order> orderList = ...;
// 显式类型
for (Order order : orderList) {
    ...
}
// 使用 var
for (var order : orderList) { // order 推断成Order类型
    ...
}
复制代码

14. var类型能够和Java 8中的Stream一起用

将Java10中的var与Java 8中的Stream结合起来非常简单。

你需要使用var取代显式类型Stream:

例1:

// 显式类型
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);                
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
// 使用 var
var numbers = Stream.of(1, 2, 3, 4, 5); // 推断为 Stream<Integer>               
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
复制代码

例2:

// 显式类型
Stream<String> paths = Files.lines(Path.of("..."));
List<File> files = paths.map(p -> new File(p)).collect(toList());
// 使用 var
var paths = Files.lines(Path.of("...")); // 推断为 Stream<String>
var files = paths.map(p -> new File(p)).collect(toList()); // 推断为 List<File>
复制代码

15. var类型可用于声明局部变量,可用于分解表达式嵌套/长链

大的或者嵌套的表达看起来令人印象深刻,通常它们被认为是聪明的代码。有时候我们会故意这么写,有时候我们从一个小表达式开始写,慢慢越来越大。为了提高代码可读性,建议用局部变量来破坏大型/嵌套表达式。但有时候,添加这些局部变量是我们想要避免的体力活。如下:

避免:

List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
int result = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0))
    .values()
    .stream()
    .max(Comparator.comparing(List::size))
    .orElse(Collections.emptyList())
    .stream()
    .mapToInt(Integer::intValue)
    .sum();
复制代码

更好:

List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
Map<Boolean, List<Integer>> evenAndOdd = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0));
Optional<List<Integer>> evenOrOdd = evenAndOdd.values()
    .stream()
    .max(Comparator.comparing(List::size));
int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
    .stream()
    .mapToInt(Integer::intValue)
    .sum();
复制代码

第二段代码可读性更强,更简洁,但是第一段代码也是对的。我们的思维会适应这样的大表达式并且更喜欢它们而不是局部变量。然而,使用var类型对于使用局部变量的方式来说是一个优化,因为它节省了获取显式类型的时间。

更好:

List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
Map<Boolean, List<Integer>> evenAndOdd = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0));
Optional<List<Integer>> evenOrOdd = evenAndOdd.values()
    .stream()
    .max(Comparator.comparing(List::size));
int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
    .stream()
    .mapToInt(Integer::intValue)
    .sum();
复制代码

16. var类型不能被用于方法返回类型或者方法参数类型

试着写下面的两段代码,编译通不过。

使用var作为方法返回类型:

// 编译通不过
public var countItems(Order order, long timestamp) {
    ...        
}
复制代码

使用var作为方法参数类型:

// 编译通不过
public int countItems(var order, var timestamp) {
    ...  
}
复制代码

17. var类型的局部变量可以用来传入到方法参数,也可以用来存放方法返回值

下面这两段代码能够编译而且运行:

public int countItems(Order order, long timestamp) {
    ...
}
public boolean checkOrder() {
    var order = ...;     // Order实例
    var timestamp = ...; // long类型的 timestamp
    var itemsNr = countItems(order, timestamp); // 推断为int类型
    ...
}
复制代码

它也适用于泛型。下面的代码片段也是对的:

public <A, B> B contains(A container, B tocontain) {
    ...
}
var order = ...;   // Order实例
var product = ...; // Product实例
var resultProduct = contains(order, product); // inferred as Product type
复制代码

18. var类型能和匿名类一起使用

避免:

public interface Weighter {
    int getWeight(Product product);
}
Weighter weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        ...
    }
};
Product product = ...; // Product实例
int weight = weighter.getWeight(product);
复制代码

更好:

public interface Weighter {
    int getWeight(Product product);
}
var weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        ...
    }
};
var product = ...; // Product实例
var weight = weighter.getWeight(product);
复制代码

19. var类型可以是Effectively Final

从Java SE 8开始,局部类可以访问封闭块内final或者effectively final的参数。变量初始化后不再改变的参数为effectively final。

所以,var类型的变量可以是effectively final的。我们可以从以下代码中看到。

避免:

public interface Weighter {
    int getWeight(Product product);
}
int ratio = 5; // 这是effectively final
Weighter weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        return ratio * ...;
    }
};
ratio = 3; // 这行赋值语句会报错
复制代码

更好:

public interface Weighter {
    int getWeight(Product product);
}
// PREFER
var ratio = 5; // 这是effectively final
var weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        return ratio * ...;
    }
};
ratio = 3; // 这行赋值语句会报错
复制代码

20. var类型可以用final修饰

默认情况下,var类型的局部变量可以被重新赋值(除非它是effectively final的)。但是我们可以声明它为final类型,如下:

避免:

public void discount(int price) {
    final int limit = 2000;
    final int discount = 5;
    if (price > limit) {
        discount++; // 这行会报错
    }
}
复制代码

更好:

public void discount(int price) {
    final var limit = 2000;
    final var discount = 5;
    if (price > limit) {
        discount++; // 这行会报错
    }
}
复制代码

21. Lambda表达式和方法引用需要显示对象类型

当对应的类型推断不出来时不能使用var类型。所以,lambda表达式和方法引用初始化不被允许。这是var类型限制的一部分。

下面的代码无法编译:

// 编译不通过
// lambda表达式需要显式目标类型
var f = x -> x + 1;
// 方法引用需要显式目标类型
var exception = IllegalArgumentException::new;
复制代码

用以下代码代替:

Function<Integer, Integer> f = x -> x + 1;
Supplier<IllegalArgumentException> exception = IllegalArgumentException::new;
复制代码

但是在lambda的内容中,Java 11允许我们去使用var作为lambda参数。例如,以下代码在Java 11中可以很好的工作(详见JEP 323(lambda参数中的局部变量))

// Java 11
(var x, var y) -> x + y
// or 
(@Nonnull var x, @Nonnull var y) -> x + y
复制代码

22. 为var类型赋值为null是不被允许的

此外,也不允许缺少初始化程序。这是var类型的另一个限制。

以下代码不会编译通过(赋值null):

// 编译通不过
var message = null; // 类型错误: 变量初始化为'null'
复制代码

这个代码也不会编译通过(缺少初始化):

// IT DOESN'T COMPILE
var message; // 使用var不能不做初始化
...
message = "hello";
复制代码

更好:

String message = null;
// or
String message;
...
message = "hello";
复制代码

23. var类型不能作为成员属性(Field)

var类型可以用来做局部变量,但是不能用来做对象的域/全局变量。

这个限制会导致这里的编译错误:

// 编译通不过
public class Product {
    private var price; // 'var' 不被允许
    private var name;  // 'var' 不被允许
    ...
}
复制代码

用以下代码代替:

public class Product {
    private int price; 
    private String name;
    ...
}
复制代码

24. var不被允许在catch块中使用

但是它被允许在try-with-resources中。

catch块

当代码抛出异常时,我们必须通过显式类型catch它,因为var类型不被允许。这个限制会导致以下代码的编译时错误:

// 编译通不过
try {
    TimeUnit.NANOSECONDS.sleep(5000);
} catch (var ex) {
    ...
}
复制代码

用这个取代:

try {
    TimeUnit.NANOSECONDS.sleep(5000);
} catch (InterruptedException ex) {
    ...
}
复制代码

try-with-resources

另一方面,var类型可以用在try-with-resource中,例如:

// 显式类型
try (PrintWriter writer = new PrintWriter(new File("welcome.txt"))) {
    writer.println("Welcome message");
}
复制代码

可以用var重写:

// 使用 var
try (var writer = new PrintWriter(new File("welcome.txt"))) {
    writer.println("Welcome message");
}
复制代码

25. var类型可以和泛型T一起使用

假定我们有下面的代码:

public <T extends Number> T add(T t) {
     T temp = t;
     ...
     return temp;   
}
复制代码

这种情况下,使用var的运行结果是符合预期的,我们可以用var替换T,如下:

public <T extends Number> T add(T t) {
     var temp = t;
     ...
     return temp;   
}
复制代码

我们看一下另一个var能够成功使用的例子,如下:

public <T extends Number> T add(T t) {
     List<T> numbers = new ArrayList<>();
     numbers.add((T) Integer.valueOf(3));
     numbers.add((T) Double.valueOf(3.9));
     numbers.add(t);
     numbers.add("5"); // 错误:类型不兼容,string不能转为T
     ...     
}
复制代码

可以用var取代List, 如下:

public <T extends Number> T add(T t) {
     var numbers = new ArrayList<T>();
     // DON'T DO THIS, DON'T FORGET THE, T
     var numbers = new ArrayList<>();
     numbers.add((T) Integer.valueOf(3));
     numbers.add((T) Double.valueOf(3.9));
     numbers.add(t);
     numbers.add("5"); // // 错误:类型不兼容,string不能转为T
     ...     
}
复制代码

这么做是安全的:

// 显式类型
Class<?> clazz = Integer.class;
// 使用var
var clazz = Integer.class;
复制代码

但是,不要因为代码中有错误,而var可以让它们魔法般的消失,就使用var取代Foo。看下一个例子,不是非常明显,但是我想让它指出核心。考虑一下当你编写这一段代码的过程,也许,你尝试定义一个String的ArrayList,并最终定义成了Collection。

26. 使用带有var类型的通配符(?),协变和你变时要特别注意

使用 ?通配符

// 显式类型
Collection<?> stuff = new ArrayList<>();
stuff.add("hello"); // 编译错误
stuff.add("world"); // 编译错误
// 使用var,错误会消失,但是我不确定你是你想要的结果
var stuff = new ArrayList<>();
strings.add("hello"); // 错误消失
strings.add("world"); // 错误消失
复制代码

Java 协变(Foo<? extends T>)和 逆变(Foo<? super T>)

我们知道可以这么写:

// 显式类型
Class<? extends Number> intNumber = Integer.class;
Class<? super FilterReader> fileReader = Reader.class;
复制代码

而且如果我们错误赋值了错误的类型,接收到一个编译时错误,这就是我们想要的:

// 编译通不过
// 错误: Class<Reader> 不能转换到 Class<? extends Number>
Class<? extends Number> intNumber = Reader.class;
// 错误: Class<Integer> 不能转化到Class<? super FilterReader>
Class<? super FilterReader> fileReader = Integer.class;
复制代码

但是如果我们使用var:

// using var
var intNumber = Integer.class;
var fileReader = Reader.class;
复制代码

然后我们可以为这些变量赋值任何类,因此我们的边界/约束消失了,这并不是我们想要的:

// 编译通过
var intNumber = Reader.class;
var fileReader = Integer.class;
复制代码

本篇大篇幅复制于: www.cnblogs.com/shenpengyan…

欢迎扫码或微信搜索公众号《程序员果果》关注我,关注有惊喜~

JDK11 | 第三篇 : 局部变量类型推断

以上所述就是小编给大家介绍的《JDK11 | 第三篇 : 局部变量类型推断》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

XML Hacks

XML Hacks

Michael Fitzgerald / O'Reilly Media, Inc. / 2004-07-27 / USD 24.95

Developers and system administrators alike are uncovering the true power of XML, the Extensible Markup Language that enables data to be sent over the Internet from one computer platform to another or ......一起来看看 《XML Hacks》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具