Java 10:局部变量类型推断
在本文中,我们将深入研究Java 10中引入的局部变量类型推断的新特性。我们将讨论使用局部变量类型推断的范围和局限性。
此功能是作为JEP(JDK增强提案)的一部分提出的:286。该提案用于增强语言以支持对局部变量声明和初始化的类型推断。
有关Java 10发行版的完整概述,请参阅Java 10功能。
目录[ 隐藏 ]
Java 10:局部变量类型推断
使用Java 10,您可以使用var
局部变量而不是键入的名称(Manifest Type)。这是通过称为局部变量类型推断的新特征完成的。
但首先,什么是类型推断?
类型推断是Java编译器查看每个方法调用和相应声明的能力,以确定使调用适用的类型参数(或参数)。类型推断不是Java编程。
对于使用初始化程序的局部变量声明,我们现在可以使用保留类型名称“var”而不是清单类型。我们来看几个例子。
var list = new ArrayList<String>(); // infers ArrayList<String>
var stream = list.stream(); // infers Stream<String>
清单类型:声明的每个变量的类型的显式标识称为清单类型。例如,如果变量“actors”要存储Actor的List,那么它的类型List <Actor>是清单类型,并且必须在Java 10之前声明它(如下所述):
List<Actor> actors = List.of(new Actor()); // Pre Java 10
var actors = List.of(new Actor()); // Java 10 onwards
局部变量类型推断如何工作?
解析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();
}
这里编译器正确地从右侧表达式推断出变量的类型,并将其添加到字节码中。
var是保留类型名称
var不是关键字,它是保留类型名称。这是什么意思?
- 我们可以创建一个名为“var”的变量。
var var = 5; // syntactically correct // var is the name of the variable
- 允许使用“var”作为方法名称。
public static void var() { // syntactically correct }
- 允许使用“var”作为包名。
package var; // syntactically correct
- “var”不能用作类或接口的名称。
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
本地变量类型推断使用场景
本地类型推断只能在以下场景中使用:
- 仅限于具有初始化程序的本地变量
- 的指数增强的for循环或索引
- 本地声明为for循环
让我们来看看这些场景的示例:
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));
}
局部变量类型推理限制
使用var有一些限制,让我们来看看其中的一些。
- 没有初始化器,不能对变量使用'var'
如果没有initailizer,那么编译器将无法推断出类型。
var x; LocalTypeInference.java:37: error: cannot infer type for local variable x var x; ^ (cannot use 'var' on variable without initializer) 1 error
- 不能用于多变量定义
var x = 5, y = 10; LocalTypeInference.java:41: error: 'var' is not allowed in a compound declaration var x = 5, y = 10; ^ 1 error
- Null不能用作var的初始化器
Null不是类型,因此编译器无法推断RHS表达式的类型。
var author = null; // Null cannot be inferred to a type LocalTypeInference.java:47: error: cannot infer type for local variable author var author = null; ^ (variable initializer is 'null') 1 error
- 不能有额外的数组维度括号
var actorArr[] = new Actor[10]; LocalTypeInference.java:52: error: 'var' is not allowed as an element type of an array var actorArr[] = new Actor[10]; ^ 1 error
- 具有lambdas,方法引用和数组初始值设定项的Poly表达式将触发错误
对于Lambda表达式,方法推理和数组初始值设定项的类型推断,编译器依赖于左侧表达式或传递表达式的方法的参数定义,而var使用RHS,这将形成循环推断,因此编译器生成编译时错误。
var min = (a, b) -> a < b ? a : b; LocalTypeInference.java:59: error: cannot infer type for local variable min var min = (a, b) -> a < b ? a : b; ^ (lambda expression needs an explicit target-type) 1 error var minimum = Math::min; LocalTypeInference.java:65: error: cannot infer type for local variable minimum var minimum = Math::min; ^ (method reference needs an explicit target-type) 1 error var nums = {1,2,3,4,5}; LocalTypeInference.java:71: error: cannot infer type for local variable nums var nums = {1,2,3,4,5}; ^ (array initializer needs an explicit target-type) 1 error
具有局部变量类型推断的泛型
Java具有泛型的类型推断,除此之外,它还必须为任何泛型语句执行类型擦除。在使用Generics的本地类型引用时,应该理解一些边缘情况。
类型擦除:为了实现泛型,Java编译器应用类型擦除,如果类型参数是无界的,则用泛化或对象替换泛型类型中的所有类型参数。
让我们通过泛型来讨论var的一些用例:
var map1 = new HashMap(); // Inferred as HashMap
var map2 = new HashMap<>(); // Inferred as HashMap<Object, Object>
map1
- 编译器将地图推断为HashMap,不带任何泛型类型。
map2
- 菱形运算符依赖于LHS进行类型推断,这里编译器无法推断LHS,因此它推断map2具有可以表示HashMap的上限或超类型。这导致map2被推断为HashMap。
匿名类类型
无法命名匿名类类型,但它们很容易理解 - 它们只是类。允许变量具有匿名类类型引入了用于声明本地类的单例实例的有用简写。我们来看一个例子:
var runnable = new Runnable() {
@Override
public void run() {
var numbers = List.of(5, 4, 3, 2, 1);
for (var number : numbers) {
System.out.println(number);
}
}
};
runThread(runnable);
不可表达的类型
无法推断到特定类型的表达式称为Non Nototable Type。对于捕获变量类型,交集类型或匿名类类型,可以发生这种类型。让我们理解一个非可表达类型如何用于局部变量类型推断:
var map3 = new HashMap<>() { // anonymous class
int someVar;
};
这里,当菱形运算符与匿名类类型一起使用时,编译器无法将RHS表达式推断为任何特定类型。这导致形成不可表示的类型。
首先,编译器将通过使用HashMap <>的超类型来获得可表示的类型,即HashMap <Object,Object>。
其次,应用匿名类扩展。最后,这将成为一个不可表示的类型,它被分配给map3。
现在可以创建一个特殊的Non Denotable类型,它不能在Java中创建。匿名扩展Object类并在其中添加属性会创建类似POJO的类,可以将其分配给变量以保存上下文。这在使用动态创建的对象时非常有用,该对象可以在临时上下文中具有结构。我们来看一个例子:
// Special Case Non Denotable Type
var person = new Object() {
class Name {
String firstName;
String lastName;
public Name(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
Name name;
Actor actor;
public String displayName() {
return name.getFirstName() + " " + name.lastName;
}
};
person.name = person.new Name("Rakesh", "Kumar");
System.out.println(person.displayName());
为局部变量类型推断选择var的一些有趣事实
有一个调查的关键字列表可供选择,为本地类型推断。以下是向社区用户提供的语法选项列表:
- var x = expr only(如C#)
- var,加上不可变本地的val (比如Scala,Kotlin)
- var,加上让不可变的本地人(比如Swift)
- auto 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的意图。
局部变量类型推断的好处
- 它改善了开发人员的体验
- 它减少了代码仪式
- 它减少了锅炉板代码
- 提高代码清晰度
结论
在本文中,我们通过Local Type Inference以及为什么选择var作为语法选项。像往常一样,您可以在这里查看github上的完整代码。
翻译&转载:https://www.journaldev.com/19871/java-10-local-variable-type-inference