英文:
Var keyword in Java
问题
使用Java 10或更高版本,我们可以使用var
关键字进行声明。在初始化时,编译器会推断出一种类型。
当我实例化并将其分配给使用var
声明的变量的类是接口的实现时,会发生什么?编译器会推断出哪种类型,接口还是实现?
英文:
With Java 10 or +, we can use var keyword for declaration. At initialization, a type is going to be inferred by the compiler.
What happens when the class I instantiate and assign to the variable declared with var
, is the implementation of the interface? which type is it going to be inferred, Interface or the implementation?
答案1
得分: 30
var
不是Java关键字。它是一个保留的类型名称。这似乎没有太大的区别,但实际上是有区别的:
var var = 0;
这里的var
也是一个变量名,所以var
可以用作类型名称,但不像常规关键字那样有限制(也就是说,我们也可以有一个名为var
的变量)。
- 变量的实际类型由Java编译器在变量声明的行上在编译时决定,但类型实际上并不是您在表达式右侧使用的确切实现。看一下这段代码:
var i = true ? Integer.valueOf(1) : "ABC";
Java编译器需要为变量i
选择一种类型,以满足两个分支的要求。它可以是a)Object
,b)Serializable
,c)Comparable
,或者是这三者的组合,或者是全部三者。我们不关心,也不知道。
英文:
My 2 cents to correct the question and answers:
- The
var
is NOT a Java keyword. It's a reserved type name. It seems not a big difference but in fact, it IS:
var var = 0;
Here var
is a variable name too, so the var
can be used as a type name, but there is no restriction like for regular keyword (i.e. we can have a variable named var
too).
- The actual type of the variable IS decided by the Java compiler on the line where the variable declared, at compile-time, but the type is NOT actually the exact implementation you use on the right side of the expression. See this code:
var i = true ? Integer.valueOf(1) : "ABC";
The Java compiler needs to pick a type for variable i
which will satisfy both branches. It could be a) Object
, b) Serializable
, c) Comparable
, or combination, or all three. We don't care and don't know.
答案2
得分: 19
回答你的问题:
当我实例化并将其分配给用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()));
根据官方JEP文档,比以下代码更难阅读:
var url = new URL("http://www.oracle.com/");
var conn = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
实现细节:
var
引入的目的是为了提供更清晰/更易读的代码,而不是为了修改Java语言的类型系统。这就是为什么你不能在类级别(用于静态或实例)字段中使用它的原因,因为字段可能与多态性一起使用(可能接收不同的实现类型,可能由IoC注入等等),而在字节码中,不可能为每种不同情况动态更改字段类型。
更新:
为什么我认为“var”不好,为什么我尽量避免使用它?
嗯,有几个原因:
- 代码实际上变得更难阅读,不仅仅是在阅读时,还在理解时,你可能会在尝试理解你得到的实际类型时遇到困难。想象一下,如果实际的对象创建表达式是一些长的构建器模式代码..或工厂模式调用..等等,你基本上必须打开/反编译源代码并查看你得到了什么。所以,这更难以阅读和理解;
- 这经常会导致(尤其是在Java方面不太有经验的工程师)对于类型是否变得动态或保持静态感到困惑;
- 不支持多态调用,一般情况下也不可能使用多态性,因为编译器无法通过相同的代码推断不同的类型;
- 代码实际上变得不一致,因为你将为字段使用显式类型,而为局部变量使用“var”。这也会(但不仅仅是)使Java的新手感到困惑;
- 也许不是那么重要..但这也可能很重要:var将破坏向后兼容性,因为你将无法降级到低于10的Java版本。
英文:
Answer to your question:
>What happens when the class I instantiate and assign to the variable declared with var, is the implementation of the interface? which type is it going to be inferred, Interface or the implementation?
Inferred type of the variable will be exactly the class, reference to the instance of which you're assigning to the variable declared with var
.
Regarding "interface" point: if it (class of the object, reference to which you assign to variable declared with "var") is an implementation of some interface, what do you think will happen if that class implements two or more interfaces? how, do you think, Java should decide on which super-type (i.e. interface) to infer for the variable? this is impossible even by basing our judgement purely on a logic (without any relation to Java) - there will be no "source of truth", by which, Java would be able to decide on this.
Motivation for var keyword:
Remember, that the only stated purpose of using var
, according to the language designers (since JDK 10+), has been the point to beautify the code and make it more readable (which, I personally think, is a non-sense as having "var" in Java, is worse than having explicit types, and in many ways so).
For example, this code:
URL url = new URL("http://www.oracle.com/");
URLConnection conn = url.openConnection();
Reader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
is (according to official JEP document) less clear to read than:
var url = new URL("http://www.oracle.com/");
var conn = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
Implementation details:
var
was introduced with the purpose of serve the cleaner/better readability and NOT to amend the Type System of the Java language. That's why, you can't use it in the class level (for static or instance) fields, because fields might be used with polymorphism (might receive different implementation types, might be injected by IoC, or etc.) and at the bytecode, it's not possible to dynamically change the field type for each different case.
Update:
Why I think "var" is bad and why I try to avoid it?
Well, for several reasons:
- code gets actually harder to read, and not only in a sense of merely reading it, but also in a sense, that you may have hard times when trying to understand what is the actual type you are getting the reference to. Imagine if the actual object creation expression is some long builder pattern code.. or factory pattern call.. or etc. you basically have to go and open/decompile the source and see what you get. So, it's harder to read and harder to understand;
- this will often lead (especially a bit less experienced in Java) engineers to confusion whether typing becomes dynamic or keeps static;
- polymorphic calls are not supported and polymorphism, in general, is not possible, because compiler cannot infer different types with the same code;
- code actually gets inconsistent, as you'll be having explicit types for fields and "var"s for local variables. This will also (but not only) confuse newcomers to Java;
- maybe not that important.. but this can be important as well: "var" will also break backwards compatibility, as you won't be able to downgrade to the Java version below 10.
答案3
得分: 9
其他回答迄今为止没有强调一个重要点,那就是可编译时推断的类型与运行时实例类之间的区别。
假设我们有
var data = Collections.singleton("test");
然后,编译器可以看到Collections.singleton("test")
声明返回Set<String>
。因此,data
实际上被声明为 Set<String>
(不是 Collection<String>
,也不是 Object
,也不是 Collections.SingletonSet
)。Set<String>
是编译器可以找到的最具体信息。
在运行时,data
引用的实例将是某个实现类(例如 Collections.SingletonSet
),由 Collections.singleton()
方法决定,这意味着 data.getClass()
不会返回 Set
类,而是返回实现 Set
接口的某个不同的东西,即 Collections.SingletonSet
。
因此,我们必须考虑三种类型:
- 被分配给变量的实例的运行时类(例如
Collections.SingletonSet
)。当然,这始终与(2)和(3)兼容,并且在编译时无法知道。 - 分配给变量的表达式的编译时类型(例如
Set<String>
)。 - 变量的声明类型。经典地,你会在变量名称的左边找到它,当使用
var
关键字时,编译器会将其视为从(2)中的类型声明(即Set<String>
)。
英文:
The other answers so far don't stress one important point, the distinction between compile-time deducible type and run-time instance class.
Suppose we have
var data = Collections.singleton("test");
Then, the compiler can see that Collections.singleton("test")
is declared to return Set<String>
. So data
effectively gets declared as Set<String>
(not e.g. Collection<String>
nor Object
nor Collections.SingletonSet
). Set<String>
is the most specific info the compiler can find out.
When running, the instance referenced in data
will be of some implementation class (e.g. Collections.SingletonSet
), decided upon by the Collections.singleton()
method, meaning that data.getClass()
will not return the Set
class, but something different that implements the Set
interface, i.e. Collections.SingletonSet
.
So we have to consider three types:
- The run-time class of the instance that gets assigned to the variable (e.g. a
Collections.SingletonSet
). This will of course always be compatible with (2) and (3), and cannot be known at compile-time. - The compile-time type of the expression that you assign to the variable (e.g.
Set<String>
). - The declared type of the variable. Classically, you'd find that explicitly left of the variable name, and when using the
var
keyword, the compiler treats it as if declared with the type from (2) (i.e.Set<String>
).
答案4
得分: 5
'official' 本地变量类型推断风格指南 (https://openjdk.java.net/projects/amber/LVTIstyle.html) 提出了这一关注点,但是指南中指出:
> “必须在此重申,var 只能用于局部变量。不能用于推断字段类型、方法参数类型和方法返回类型。在这些情况下,“编程到接口”的原则仍然与以往一样重要。”
以及
> “如果按照指南 G2 中的建议,局部变量的作用域很小,则来自具体实现的“泄漏”可能会影响后续代码的风险是有限的。”
英文:
The 'official' Local Variable Type Inference style guide (https://openjdk.java.net/projects/amber/LVTIstyle.html) raises this concern, but says:
> "It must be reiterated here that var can only be used for local
> variables. It cannot be used to infer field types, method parameter
> types, and method return types. The principle of "programming to the
> interface" is still as important as ever in those contexts."
and
> "If, as recommended in guideline G2, the scope of the local variable
> is small, the risks from "leakage" of the concrete implementation that
> can impact the subsequent code are limited."
答案5
得分: 0
类型将与变量被赋值的值完全相同。
如果你更关注接口而不是实现的思想:你可以创建一个函数,该函数返回接口类型,但在内部创建特定的实现实例。
英文:
The type will be exactly the same of the value the variable gets assigned to.
In case you are more concerned with interface over implementation idea: you can create function which returns interface type but creates a specific implementation instance inside.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论