Java 10 局部变量类型推断

  在本文中,我们将深入研究Java 10中引入的局部变量类型推断的新特性。我们将讨论使用局部变量类型推断的范围和限制。

  此功能是作为JEP:286(JDK增强提案)的一部分提出的。该提案通过支持对局部变量声明和初始化的类型推断以增强Java语言。

有关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 是清单类型,并且必须在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
  • 具有lambda,方法引用和数组初始值设定项的多语句表达式将触发错误
    对于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具有泛型的类型推断,除此之外,它还必须为任何泛型语句执行类型擦除。在使用泛型的局部类型引用时,应该理解一些边界情况。

  类型擦除:为了实现泛型,Java编译器会使用类型擦除。如果类型参数是无界的,则用绑定的泛化类型或Object替换泛型类型中的所有类型参数。

让我们通过泛型来讨论var的一些用例:

var map1 = new HashMap(); // Inferred as HashMap
var map2 = new HashMap<>(); // Inferred as HashMap<Object, Object>

  map1 - 编译器将该map推断为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

// 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,及不可变局部的let(如Swift)
  • auto x = expr(如C ++)
  • const x = expr(已经是保留字)
  • final x = expr(已经是保留字)
  • let x = expr
  • def x = expr(如Groovy)
  • x:= expr(如Go)

调查结果:
java局部变量类型推断
调查选择的百分比:
java var变量

使用次优的选择(var)的理由

  • 虽然var是次优的选择,人们对它的感觉很好,几乎没有人讨厌它。而其他选择则不是这种情况。
  • C#体验。C#社区发现该关键字对于类似Java的语言来说是合理的。
  • 一些读者发现var / val是如此相似,以至于他们可以忽略差异,并且对不可变和可变变量使用不同的关键字会很烦人。
  • 大多数局部变量实际上是final的,并且用另一个形式来惩罚不变性并不是JEP的意图。

局部变量类型推断的好处

  • 它改善了开发人员的体验
  • 它减少了代码形式
  • 它减少了样板代码
  • 提高代码清晰度

原文地址:Java 10: Local Variable Type Inference written by Rakesh Kumar
完整代码:Github

猜你喜欢

转载自blog.csdn.net/why_still_confused/article/details/82593739
今日推荐