关于java:译Java局部变量类型推断Var类型的26条细则

9次阅读

共计 16439 个字符,预计需要花费 42 分钟才能阅读完成。

原文链接:https://dzone.com/articles/va…

作者:Anghel Leonard

译者:沈歌

Java 局部变量类型推断(LVTI),简称 var 类型(标识符 var 不是一个关键字,是一个预留类型名),Java 10 中通过 JEP 286: Local-Variable Type Inference 增加进来。作为 100% 编译特色,它不会影响字节码,运行时或者性能。在编译时,编译器会查看赋值语句右侧代码,从而推断出具体类型。它查看申明的右侧,如果这是一个初始化语句,它会用那个类型取代var。另外,它十分有助于缩小冗余代码和样板代码。它还只在旨在编写代码时所波及的典礼。例如,应用var evenAndOdd=... 代替Map<Boolean, List<Integer>> evenAndOdd... 十分不便。依据用例,它有一个代码可读性的衡量,会在上面第一条中提到。

此外,这里有 26 条细则,笼罩了 var 类型的用例,包含它的限度。

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

通常咱们在起全局变量名的时候会留神这一点,然而抉择局部变量名的时候不太留神。尤其是当办法很短,办法名和实现都不错的时候,咱们趋向于简化咱们的变量名。然而当咱们应用 var 代替显式类型的时候,具体的类型是通过编译器推断进去的。所以,对于人来说浏览或者了解代码十分艰难。在这一点上 var 减弱了代码可读性。这种事件之所以会产生,是因为大多数状况下,咱们会把变量类型当成是第一信息,而把变量名当成第二信息。然而应用 var 的时候,恰恰相反。

示例 1

即便到这里,一些敌人依然保持局部变量名短点好。咱们看一下:


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

咱们换成 var 时,防止:

// AVOID
public boolean callDocumentationTask() {var dtl = ToolProvider.getSystemDocumentationTool();
    var dtt = dtl.getTask(...);
    return dtt.call();}

更好:

// PREFER
public boolean callDocumentationTask() {var documentationTool = ToolProvider.getSystemDocumentationTool();
    var documentationTask = documentationTool.getTask(...);
  return documentationTask.call();}

示例 2:

防止:

// AVOID
public List<Product> fetchProducts(long userId) {var u = userRepository.findById(userId);
    var p = u.getCart();
    return p;
}

更好:

// PREFER
public List<Product> fetchProducts(long userId) {var user = userRepository.findById(userId);
    var productList = user.getCart();
    return productList;
}

示例 3:

争取为局部变量起有意义的名字并不意味着要掉入适度命名的坑,防止在短办法中应用繁多类型的数据流:


// AVOID
var byteArrayOutputStream = new ByteArrayOutputStream();

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

// PREFER
var outputStream = new ByteArrayOutputStream();
// or
var outputStreamOfFoo = new ByteArrayOutputStream();

另外,你晓得吗,Java 外部应用了一个类名字叫:InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState

额。。。命名这个类型的变量是个挑战。

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, doublefloat:

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

以上代码是很常见而且清晰的,当初咱们应用var:

防止:

// AVOID
var intNumber = 20;    // 推断为 int
var longNumber = 20;   // 推断为 int
var floatNumber = 20;  // 推断为 int
var doubleNumber = 20; // 推断为 int

四个变量都被推断成了int。为了修改这个行为,咱们须要依赖 Java 中的数据类型标记。

更好实现:

// PREFER
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, ...) {...}

当初,咱们把这两个办法汇总,提供一个服务办法。顾客抉择要买的商品,计算最优价格,而后扣款:

// AVOID
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, 咱们的代码会因为隐式类型转换而变得没有兼容性问题。

// PREFER
public void purchaseCart(long customerId) {
    ...
    var price = computeBestPrice(...);
    debitCard(price, ...);
}

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

一些 Java 根底数据类型不反对数据类型标记。例如 byteshort。应用显式根底数据类型时没有任何问题。应用 var 代替的时候:

// 这样更好,而不是应用 var
byte byteNumber = 45;     // 这是 byte 类型
short shortNumber = 4533; // 这是 short 类型

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

防止应用以下代码:

// AVOID
var byteNumber = 45;    // 推断为 int
var shortNumber = 4533; // 推断为 int

这里没有根底数据类型帮忙咱们,因而咱们须要依赖显示强制类型转换。从集体角度来讲,我会防止这么用,因为没啥益处,然而能够这么用。

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

// 如果你真的想用 var,这么写
var byteNumber = (byte) 45;     // 推断为 byte
var shortNumber = (short) 4533; // 推断为 short

5. 如果变量名没有对人来说足够的类型信息,防止应用var

应用 var 有助于提供更加简练的代码。例如, 在应用构造方法时(这是应用局部变量的常见示例),咱们能够简略地防止反复类名的必要性,从而打消冗余。

防止:

// AVOID
MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...);

更好:

// PREFER
var inputStream = new MemoryCacheImageInputStream(...);

在上面的构造中,var也是一个简化代码而不失落信息的好办法。

防止:

// AVOID
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(...);

更好:

// PREFER
var compiler = ToolProvider.getSystemJavaCompiler();
var fileManager = compiler.getStandardFileManager(...);

为什么这样基于 var 的例子咱们感觉比拟难受呢?因为须要的信息曾经在变量名中了。然而如果应用var 加上变量名,还是会失落信息,那么最好防止应用var

防止:

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

应用以下代码代替:

// PREFER
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<String>:

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<>();

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

首先应该防止这么用:

// AVOID
var productList = new ArrayList<>(); // 推断为 ArrayList<Object>

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

所以为了防止这样的情景,咱们必须提供可能推断到预测类型的信息。这个能够间接给也能够间接给。

更好的实现(间接):


// PREFER
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];

应该这么用:

// PREFER
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";

用上面的代码代替:

// PREFER
String hello = "hello", bye = "bye", welcome = "welcome";

或者这么用:

// PREFER
var hello = "hello";
var bye = "bye";
var welcome = "welcome";

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

局部变量应该放弃小作用域,我确定你在 var 呈现之前就听过这个,这样能够加强代码可读性,也不便更快的修复 bug。

例如咱们定义一个 java 栈:

防止:

// AVOID
...
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 办法,该办法继承自java.util.Vector. 这个办法将以 Vector 的形式遍历栈。当初咱们筹备切换StackArrayDeque,切换之后 forEach() 办法将变成 ArrayDeque 的,将以 stack(LIFO)的形式遍历 stack。

// AVOID
...
var stack = new ArrayDeque<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// 50 行不必 stack 的代码
// Kelly, Martin, Tyllen, George
stack.forEach(...);
...

这不是咱们想要的,咱们很难看出引入了一个谬误,因为蕴含 forEach() 局部的代码不在研发实现批改的代码左近。为了疾速修复这个谬误,并防止高低滚动来理解产生了什么,最好放大 stack 变量的作用域范畴。

最好这么写:

// PREFER
...
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 更好:

// PREFER
// 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类型可用于申明局部变量,可用于合成表达式嵌套 / 长链

var类型可用于申明局部变量,可用于合成表达式嵌套 / 长链.

大的或者嵌套的表白看起来令人印象粗浅,通常它们被认为是聪慧的代码。有时候咱们会成心这么写,有时候咱们从一个小表达式开始写,缓缓越来越大。为了进步代码可读性,倡议用局部变量来毁坏大型 / 嵌套表达式。但有时候,增加这些局部变量是咱们想要防止的体力活。如下:

防止:

List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// AVOID
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 类型对于应用局部变量的形式来说是一个优化,因为它节俭了获取显式类型的工夫。

更好

var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
var evenAndOdd = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0));
var evenOrOdd = evenAndOdd.values()
    .stream()
    .max(Comparator.comparing(List::size));
var 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类型的局部变量能够用来传入到办法参数,也能够用来寄存办法返回值

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);
}
// AVOID
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);
}
// PREFER
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);
}
// AVOID
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 类型,如下:

防止:


// AVOID
// IT DOESN'T COMPILE
public void discount(int price) {
    final int limit = 2000;
    final int discount = 5;
    if (price > limit) {discount++; // 这行会报错}
}

更好:

// PREFER
// IT DOESN'T COMPILE
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;

用以下代码代替:


// PREFER
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";

更好:

// PREFER
String message = null;
// or
String message;
...
message = "hello";

23. var类型不能作为对象的域(Field)

var类型能够用来做局部变量,然而不能用来做对象的域 / 全局变量。

这个限度会导致这里的编译谬误:

// 编译通不过
public class Product {
    private var price; // 'var' 不被容许
    private var name;  // 'var' 不被容许
    ...
}

用以下代码代替:

// PREFER
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) {...}

用这个取代:

// PREFER
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<T>, 如下:

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
     ...     
}

26. 应用带有 var 类型的通配符(?),协方差和拥护变量时要特地留神

应用?通配符

这么做是平安的:

// 显式类型
Class<?> clazz = Integer.class;
// 应用 var
var clazz = Integer.class;

然而,不要因为代码中有谬误,而 var 能够让它们魔法般的隐没,就应用 var 取代 Foo<?>。看下一个例子,不是非常明显,然而我想让它指出外围。考虑一下当你编写这一段代码的过程,兴许,你尝试定义一个 String 的 ArrayList,并最终定义成了 Collection<?>。

// 显式类型
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;

论断

var 类型十分酷,而且未来会持续降级。留心 JEP 323 和 JEP 301 理解更多。祝您欢快。

任何人想要转载我的文章,无需和我分割,请转载后把链接私信贴给我,谢谢!

正文完
 0