Java 中的 var 关键字

Har*_*nga 36 java var

对于Java 10或+,我们可以使用var关键字进行声明。在初始化时,编译器将推断类型。

当我实例化的类并分配给用 , 声明的变量var,是接口的实现时,会发生什么?将推断哪种类型,接口还是实现?

Bar*_*jeu 37

我的2美分来纠正问题和答案:

  1. 不是Javavar关键字。这是一个保留的类型名称。看起来差别不大,但实际上是:

    var var = 0;
    
    Run Code Online (Sandbox Code Playgroud)

    这里var也是一个变量名,因此var可以用作类型名,但没有像常规关键字那样的限制(即我们var也可以有一个名为的变量)。

  2. 变量的实际类型由 Java 编译器在编译时声明变量的行决定,但该类型实际上并不是您在表达式右侧使用的确切实现。看这段代码:

    var i = true ? Integer.valueOf(1) : "ABC";
    
    Run Code Online (Sandbox Code Playgroud)

    Java 编译器需要为变量选择一个类型i来满足这两个分支。它可以是 a) Object、 b) Serializable、 c)Comparable或组合,或三者皆有。我们不关心也不知道。


Gio*_*uri 24

回答你的问题:

当我实例化的类并分配给用 var 声明的变量,是接口的实现时,会发生什么?将推断哪种类型,接口还是实现?

变量的推断类型将正是,对您分配给用 声明的变量的实例的引用var

关于“接口”点:如果它(对象的类,分配给用“var”声明的变量的引用)是某个接口的实现,那么如果该类实现两个或多个接口,您认为会发生什么?您认为 Java 应该如何决定为变量推断哪个超类型(即接口)?即使我们的判断纯粹基于逻辑(与 Java 没有任何关系),这也是不可能的 - 不会有“事实来源”,Java 能够据此做出决定。


var关键字的动机:

请记住,根据语言设计者的说法(自 JDK 10+ 起),使用 的唯一目的var是美化代码并使其更具可读性(我个人认为,这是没有意义的,因为“ Java 中的 var" 比显式类型更糟糕,而且在很多方面都是如此)。

例如,这段代码:

URL url = new URL("http://www.oracle.com/"); 
URLConnection conn = url.openConnection(); 
Reader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
Run Code Online (Sandbox Code Playgroud)

(根据官方JEP 286文档)阅读起来不如:

var url = new URL("http://www.oracle.com/"); 
var conn = url.openConnection(); 
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
Run Code Online (Sandbox Code Playgroud)

实施细节:

var引入的目的是提供更清晰/更好的可读性,而不是修改 Java 语言的类型系统。这就是为什么,你不能在类级别(静态或实例)字段中使用它,因为字段可能与多态性一起使用(可能接收不同的实现类型,可能由 IoC 注入等)和字节码,不可能针对每种不同的情况动态更改字段类型。


更新:

为什么我认为“var”不好并且为什么我试图避免它?

好吧,有几个原因:

  1. 代码实际上变得更难阅读,不仅是在阅读它的意义上,而且在某种意义上,当您试图理解您所引用的实际类型是什么时,您可能会遇到困难。想象一下,如果实际的对象创建表达式是一些很长的构建器模式代码..或工厂模式调用..等等,那么您基本上必须打开/反编译源代码,然后看看会得到什么。所以,它更难阅读,更难理解;
  2. 这通常会导致(尤其是 Java 经验较少的)工程师困惑于类型是动态的还是保持静态的;
  3. 不支持多态调用,并且多态性通常是不可能的,因为编译器无法使用相同的代码推断出不同的类型;
  4. 代码实际上变得不一致,因为您将拥有明确的字段类型和局部变量的“var”。这也会(但不仅仅是)让 Java 新手感到困惑;
  5. 也许没那么重要..但这也可能很重要:“var”也会破坏向后兼容性,因为您将无法降级到低于 10 的 Java 版本。

  • 对我来说 var 使代码更难阅读,因为它隐藏了变量的类型。当我阅读代码时,我喜欢清楚地看到变量类型并显式声明,无论变量是否是本地变量。 (29认同)
  • 我同意 。var 应该从 Java 中删除。介绍的人也应该转JavaScript (9认同)
  • 说实话,我完全不同意。我绝对更喜欢`for (var entry:map.entrySet())`而不是`for (Map.Entry<Integer,SortedSet<String>> entry:map.entrySet())`。如果我确实无法从上下文中推断出类型,我仍然可以通过将鼠标悬停在“var”关键字上来使用 IDE 的帮助来查看实际类型。 (2认同)

Ral*_*off 9

到目前为止,其他答案并没有强调一个重要的点,即编译时可推导类型和运行时实例类之间的区别。

假设我们有

var data = Collections.singleton("test");
Run Code Online (Sandbox Code Playgroud)

然后,编译器可以看到Collections.singleton("test")声明为 return Set<String>。因此data实际上被声明为Set<String>(不是例如nornor Collection<String>)。是编译器可以找到的最具体的信息。ObjectCollections.SingletonSetSet<String>

运行时,引用的实例data将是某个实现类(例如Collections.SingletonSet),由方法决定Collections.singleton(),这意味着data.getClass()不会返回该类Set,而是实现接口的不同对象Set,即Collections.SingletonSet

所以我们必须考虑三种类型:

  1. 分配给变量的实例的运行时类(例如 a Collections.SingletonSet)。这当然总是与(2)和(3)兼容,并且在编译时无法得知。
  2. 分配给变量的表达式的编译时类型(例如Set<String>)。
  3. 变量的声明类型。通常,您会发现变量名称的显式左侧,并且在使用var关键字时,编译器将其视为使用 (2) 中的类型声明(即Set<String>)。


tgd*_*ies 6

“官方”局部变量类型推断风格指南(https://openjdk.java.net/projects/amber/LVTIstyle.html)提出了这个问题,但说:

“这里必须重申一下,var只能用于局部变量,不能用于推断字段类型、方法参数类型、方法返回类型。“向接口编程”的原则在这些背景。”

“如果按照准则 G2 中的建议,局部变量的范围很小,那么具体实现的“泄漏”影响后续代码的风险就会受到限制。”