使用JEP 286:Local-Variable Type Inference 将Java局部变量类型推断(LVTI)或简称var类型( var标识符不是关键字,而是保留的类型名称)添加到Java 10中。 作为100%的编译器函数,它不会影响字节码,运行时或性能。 基本上,编译器检查赋值运算符的右侧,并基于该赋值运算符确定变量的特定类型,然后将其替换为var 。
此外,它对于减少样板代码的冗长性很有用,并且还可以加快编程过程本身。 例如,写var evenAndOdd =...
而不是Map<Boolean, List<Integer>> evenAndOdd =...
非常方便var evenAndOdd =...
var的出现并不意味着在任何地方使用它总是总是方便的,有时使用标准工具会更实用。
在本文中,我们将研究26种情况,并举例说明何时可以使用var ,以及何时不值得使用var 。
要点1:尝试给局部变量起有意义的名字
通常,我们专注于为类的字段提供正确的名称,但是我们对局部变量的名称没有给予同样的关注。 当我们的方法实现完美,包含很少的代码并具有良好的名称时,那么我们常常不注意局部变量,甚至完全不使用它们的名称。
当我们使用var而不是编写显式类型时,编译器会自动检测到它们并替换var 。 但是,另一方面,由于使用var会使代码的可读性和理解复杂化,因此人们阅读和理解代码变得更加困难。 在大多数情况下,这是因为我们倾向于将变量的类型视为主要信息,而将其名称视为次要信息。 虽然应该恰恰相反。
范例1:
许多人可能会同意,在下面的示例中,局部变量的名称太短了:
当使用短名称以及var时 ,代码变得更加不清楚:
首选选项:
范例2:
避免这样命名变量:
使用更有意义的名称:
范例3:
为了给局部变量赋予更易理解的名称,请不要极端:
相反,您可以使用更简短但也不难理解的选项:
您是否知道Java有一个内部类,名为:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
好吧,用这种类型的变量命名可能很麻烦:)
第2点:使用文字帮助var查明基本类型(int,long,float,double)
在原始类型不使用文字的情况下,我们可能会发现预期类型和隐含类型可能会有所不同。 这是由var变量使用的隐式类型转换引起的。
例如,以下两个代码片段的行为符合预期。 在这里,我们显式声明boolean和char类型:
boolean flag = true;
现在我们使用var ,而不是显式声明类型:
var flag = true;
到目前为止一切顺利。 现在对int , long , float和double类型执行相同的操作:
int intNumber = 20;
尽管上面的代码段简单明了,但现在让我们使用var而不是显式指定类型。
避免:
所有四个变量都将作为int输出。 要解决此问题,我们需要使用Java文字:
但是,如果我们声明一个十进制数会怎样?
如果希望获得float类型的变量,请避免这种情况:
为避免意外,请使用适当的文字:
要点3:在某些情况下,var和隐式类型转换可以简化代码支持
例如,假设我们的代码在两个方法之间。 一种方法是获取具有不同产品的购物车,然后计算出最佳价格。 为此,他比较了市场上的各种价格,并以浮点型的形式返回了总价格。 另一种方法只是从卡中扣除此价格。
首先,让我们看一下计算最佳价格的方法:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
其次,让我们看一下适用于地图的方法:
public boolean debitCard(float amount, ...) { ... }
现在,我们将代码作为客户端放在这两个外部服务方法之间。 我们的用户可以选择要购买的商品,然后我们为他们计算最优惠的价格,然后从卡中注销资金:
一段时间后,拥有API的公司决定放弃价格的材料表示形式,转而使用小数点(现在使用float , int代替)。 因此,他们修改了API代码,如下所示:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
事实是我们的代码使用float变量的显式声明作为价格。 以当前形式,我们将在编译时收到错误。 但是,如果我们预见到了这种情况,并使用var而不是float ,那么由于隐式类型转换,我们的代码将继续正常工作:
要点4:如果文字不是合适的解决方案,请使用显式强制转换或放弃var
Java中的某些原始类型没有特殊的文字,例如字节和短类型。 在这种情况下,使用显式类型指定,我们可以毫无问题地创建变量。
使用它代替var :
但是,为什么在这种情况下优先使用显式类型表示法而不是仅使用var ? 好吧,让我们使用var编写这段代码。 请注意,在两种情况下,编译器都会假定您需要int类型的变量。
避免此错误:
这里没有任何文字可以帮助我们,因此我们被迫使用显式的向下类型转换。 就我个人而言,我会避免这种情况,因为我在这里看不到任何优势。
仅当您确实要使用var时才使用此条目:
使用var的好处是编写更简洁的代码。 例如,在使用构造函数的情况下,我们可以避免重复类名的需要,从而消除了代码冗余。
避免以下情况:
改用:
对于下面的构造, var也是简化代码而不丢失信息的好方法。
避免:
使用以下代码:
那么,为什么在给出的示例中我们更愿意使用var ? 因为所有必需的信息都包含在变量的名称中。 但是,如果var与变量名结合使用会降低代码的清晰度,则最好拒绝使用它。
避免:
用途:
例如,考虑使用java.nio.channels.Selector
类。 此类具有静态的open()
方法,该方法返回一个新的Selector并将其打开。 但是在这里您可以轻松地认为Selector.open()
方法可以返回布尔类型,这取决于打开现有选择器的成功程度,甚至可以返回void 。 在此处使用var会导致信息丢失和代码混乱。
要点6:var类型保证了编译时的安全性
这意味着我们无法编译试图执行不正确分配的应用程序。 例如,下面的代码无法编译:
但是这个编译:
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<>();
这是最常见且最理想的做法,因为我们可以轻松地用任何其他方式替换接口实现。 为此,只需要声明一个接口类型变量。
我们将无法使用var变量来遵循这个概念,因为 始终为他们显示特定类型。 例如,在下面的代码片段中,编译器会将变量的类型确定为ArrayList<String>
:
var productList = new ArrayList<String>();
有几种防御参数可以解释此行为:
var用于局部变量,在大多数情况下,使用接口编程要比使用值或字段返回的方法参数少
局部变量的范围应该很小,因此解决由于切换到另一个实现而引起的问题应该不会很困难
var将右侧的代码视为用于确定实际类型的初始化程序。 如果在某个时候更改了初始化程序,则定义的类型也可能更改,从而导致依赖此变量的代码出现问题。
要点8:意外类型的推断的可能性
在没有信息来标识类型的情况下将var与菱形运算符 (<>)结合使用可能会导致意外结果。
在Java 7之前,显式类型推断用于集合:
从Java 7开始,引入了diamond运算符 。 在这种情况下,编译器将独立派生必要的类型:
下面的代码将输出什么类型?
您应该避免这样的构造:
该类型将定义为ArrayList<Object>
。 这是因为未提供正确确定类型所需的信息。 这导致将选择最接近的类型,这可以与正在发生的情况相兼容。 在这种情况下, Object
。
因此,只有在我们提供确定所需类型的必要信息时,才能使用var 。 可以直接指定类型,也可以将其作为参数传递。
直接指定类型:
传递所需类型的参数:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
第9项:将数组分配给var变量不需要方括号[]
我们都知道如何在Java中声明数组:
int[] numbers = new int[5];
使用数组时如何使用var ? 在这种情况下,无需在左侧使用括号。
避免以下情况(甚至无法编译):
用途:
下面使用var的代码也无法编译。 这是因为编译器无法从右侧确定类型:
项目10:在同一行上声明多个变量时,不能使用var
如果您想一次声明一种类型的变量,那么您需要知道var不适合于此。 以下代码无法编译:
改用:
还是:
要点11:局部变量应努力使其范围最小。 var类型加强了该语句。
对于局部变量,请保留较小的范围-我确定您在var之前听过此声明。
可读性和快速修复错误是支持此方法的理由。 例如,让我们如下定义一个堆栈:
避免这种情况:
请注意,我们正在调用forEach()
方法,该方法继承自java.util.Vector
。 此方法将像其他任何向量一样遍历堆栈,这就是我们需要的。 但是现在我们决定使用ArrayDeque
而不是Stack
。 当我们这样做时, forEach()
方法将从ArrayDeque接收实现,该实现将作为标准堆栈(LIFO)遍历堆栈
这不是我们想要的。 在这里很难跟踪错误,因为包含forEach()
部分的代码并不位于进行更改的代码旁边。 为了提高查找和修复错误的速度,最好使用stack
变量编写代码,并尽可能靠近此变量的声明。
最佳做法如下:
现在,当开发人员从Stack
切换到ArrayQueue
,他将能够迅速注意到错误并进行修复。
第12条:var类型简化了三元运算符中各种类型的使用
我们可以在三元运算符的右侧使用不同类型的操作数。
当明确指定类型时,以下代码不会编译:
不过,我们可以这样做:
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);
以下代码也无法编译:
但是您可以使用更多常规类型:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
在所有这些情况下,最好使用var :
从这些示例中不能得出var类型在运行时定义对象类型的结论。 事实并非如此!
而且,当然, var类型将与两个操作数的相同类型一起正常工作:
要点13:var类型可以在循环内使用
我们可以轻松地将for循环中类型的显式声明替换为 var类型。
将显式int类型更改为var :
将Order
的显式类型更改为var :
List<Order> orderList = ...;
要点14:var在Java 8中可以很好地处理流
将Java 10中的var与Java 8中出现的流一起使用非常简单。
您可以简单地用var替换Stream类型的显式声明:
范例1:
范例2:
第15条:在声明旨在将大型表达式链分解成多个部分的局部变量时,可以使用var
带有大量嵌套的表达式看起来令人印象深刻,通常看起来像是某种智能且重要的代码段。 在需要提高代码可读性的情况下,建议使用局部变量将大表达式分解。 但是有时候写很多局部变量似乎是一项非常累人的工作,我想避免。
一个大型表达式的示例:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
最好将代码分解为各个组成部分:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
该代码的第二个版本看起来更易读,更简单,但是第一个版本也有权使用。 对我们来说,适应如此大的表达式并偏向于局部变量更喜欢我们。 但是,使用var类型可以通过减少声明局部变量的工作量来帮助破坏大型结构:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
第16条:var不能用作返回类型或方法参数类型
下面显示的两个代码段将无法编译。
使用var作为返回类型:
使用var作为方法参数的类型:
第17条:var类型的局部变量可以作为方法的参数传递,也可以采用方法返回的值
以下代码片段将编译并正常运行:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
… Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <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");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . – :
结论
« var », Java 10. , . , var , .
var Java!