Java 10:局部变量类型推断
在本文中,我们将深入研究Java 10中引入的Local-Variable Type Inference的新功能。
我们将探讨使用局部变量类型推断的范围和局限性。
此功能是作为JEP(JDK增强建议):286的一部分提出的。
该建议是为了增强语言以支持对局部变量声明和初始化的类型推断。
1. Java 10:局部变量类型推断
在Java 10中,您可以将var用作局部变量,而不是使用类型名称(列表类型)。
这是通过称为局部变量类型推断的新功能完成的。
但是首先,什么是类型推断?
类型推断是Java编译器查看每个方法调用和相应声明以确定使调用适用的类型参数的能力。
类型推断不适用于Java编程。
对于带有初始化程序的局部变量声明,我们现在可以使用保留类型名称" var"而不是列表类型。
让我们看一些例子。
var list = new ArrayList<String>(); //infers ArrayList<String> var stream = list.stream(); //infers Stream<String>
列表类型:对要声明的每个变量的类型的显式标识称为列表类型。
例如,如果变量" actors"要存储一个Actor列表,则其类型List <Actor>是列表类型,并且必须在Java 10之前声明它(如下所述):
List<Actor> actors = List.of(new Actor()); //Pre Java 10 var actors = List.of(new Actor()); //Java 10 onwards
2.局部变量类型推断如何工作?
解析var语句后,编译器将查看声明的右侧(也称为初始化程序),并从右侧(RHS)表达式推断类型。
好的,这是否意味着Java现在是一种动态类型的语言?并非如此,它仍然是静态类型的语言。
让我们以一段代码来读取文件。
private static void readFile() throws IOException { var fileName = "Sample.txt"; var line = ""; var fileReader = new FileReader(fileName); var bufferedReader = new BufferedReader(fileReader); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } bufferedReader.close(); }
现在,让我们看一下从IntelliJ IDEA反编译器获取的反编译代码。
private static void readFile() throws IOException { String fileName = "Sample.txt"; String line = ""; FileReader fileReader = new FileReader(fileName); BufferedReader bufferedReader = new BufferedReader(fileReader); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } bufferedReader.close(); }
其中编译器会从右侧表达式中正确推断出变量的类型,并将其添加到字节码中。
3. var是保留类型名称
var不是关键字,它是保留的类型名称。
这是什么意思?
我们可以创建一个名为" var"的变量。
允许使用" var"作为方法名称。
允许使用" var"作为程序包名称。
" var"不能用作类或者接口的名称。
4.局部变量类型推断使用方案
本地类型推断只能在以下情况下使用:
- 仅限于带有初始化程序的局部变量
- for循环或者索引的增强索引
- 本地在for循环中声明
让我们来看一下这些情况的示例:
var var = 5; //syntactically correct //var is the name of the variable
5.局部变量类型推断限制
使用var有一定的局限性,让我们来看一些。
如果没有初始化程序,则无法在变量上使用" var"。
如果没有初始化程序,则编译器将无法推断类型。不能用于多个变量定义
Null不能用作初始化器,因为varNull不是类型,因此编译器无法推断RHS表达式的类型。
不能有另外的数组尺寸括号
具有lambda,方法引用和数组初始值设定项的多边形表达式将触发错误对于Lambda表达式,方法推断和Array初始值设定项的类型推断,编译器依赖于左侧表达式或者该表达式所在的方法的参数定义在var使用RHS时传递,这将形成循环推断,因此编译器会产生编译时错误。
6.具有局部变量类型推断的泛型
Java具有泛型的类型推断,最重要的是,它还必须对任何泛型语句进行类型擦除。
将局部类型引用与泛型一起使用时,应了解一些边缘情况。
类型擦除:为了实现泛型,Java编译器将类型擦除应用于,将泛型类型中的所有类型参数都用其边界或者对象(如果类型参数是无界的)替换。
我们来看一下使用泛型的var用例:
public static void var() { //syntactically correct }
map1 –编译器将地图推断为HashMap,没有任何泛型类型。
" map2" – Diamond运算符依赖LHS进行类型推断,其中编译器无法推断LHS,因此,它推断map2具有可将HashMap表示为的上限或者超类型。
这导致map2被推断为HashMap。
7.匿名类类型
匿名类类型无法命名,但它们很容易理解-它们只是类。
允许变量具有匿名类类型为声明局部类的单例实例引入了有用的快捷方式。
让我们看一个例子:
package var; //syntactically correct
8.非限定类型
无法推断为特定类型的表达式称为非指定类型。
对于捕获变量类型,交集类型或者匿名类类型,可能会出现这种类型。
让我们了解如何将非指定类型用于局部变量类型推断:
class var{ } //Compile Error LocalTypeInference.java:45: error: 'var' not allowed here class var{ ^ as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations 1 error interface var{ } //Compile Error
其中当将菱形运算符与匿名类类型一起使用时,编译器无法将RHS表达式推断为任何特定类型。
这导致形成不可表示的类型。
首先,编译器将通过使用HashMap <>的超类型(即HashMap <Object,Object>)来获得可表示类型。
其次,应用匿名类扩展。
最终,这成为分配给map3的不可表示类型。
现在可以创建无法在Java中更早创建的Non-Denotable类型的特殊情况。
匿名扩展Object类并其中添加属性会创建一个类似于类的POJO,可以将其分配给变量以保存上下文。
这在使用可以在临时上下文中具有结构的动态创建的对象时非常有用。
让我们来看一个例子:
var numbers = List.of(1, 2, 3, 4, 5); //inferred value ArrayList<String> //Index of Enhanced For Loop for (var number : numbers) { System.out.println(number); } //Local variable declared in a loop for (var i = 0; i < numbers.size(); i++) { System.out.println(numbers.get(i)); }
9.为局部变量类型推断选择var的一些有趣事实
针对本地类型推断进行了一项针对可供选择的关键字列表的调查。
以下是提供给社区用户的语法选项列表:
- var x =仅expr(如C#)
- var,加上不可变的当地人的val(例如Scala,Kotlin)
- var,再加上不可变的本地变量(如Swift)
- 自动x = expr(如C++)
- const x = expr(已保留字)
- final x = expr(已保留字)
- 令x = expr
- def x = expr(例如Groovy)
- x:= expr(如Go)
调查结果:百分比的选择响应:
使用第二最佳选择的理由(var)
尽管var是第二好的选择,但人们对此表示满意,几乎没有人讨厌它。
而其他选项则并非如此。有C#经验。
C#社区发现该关键字对于类似Java的语言来说是合理的。一些读者发现var/val非常相似,以至于他们几乎可以忽略它们之间的差异,并且将不同的关键字用于不可变和可变的变量会很烦人。
多数局部变量实际上是最终变量,而用另一种仪式惩罚不变性不是JEP的意图。
10.局部变量类型推断的好处
- 它改善了开发人员的体验
- 减少代码仪式
- 减少样板代码
- 提高代码清晰度