I've tried to scan JEP-286 about local type inference. I see that this works only for local variables - understood. So this does work indeed:
public class TestClass {
public static void main(String [] args){
var list = new ArrayList<>();
list.add("1");
System.out.println(list.get(0)); // 1
}
}
I do see that this on the other hand does not compile:
public class TestClass {
public var list = new ArrayList<>();
public static void main(String [] args){
}
}
It's obvious that it does not, since the JEP says so. Now my question:
It makes perfect sense for a public/protected member declared as var
to fail, at least IMO. But why does it not compile even if it's private
? I can only assume that you can still get a hold of that variable via reflection (and I can't get local fields like this)... And getting that variable would require a cast, well, a very confused cast probably.
The motivation for forbidding type inference for fields and method returns is that APIs should be stable; field access and method invocation are linked by descriptor at runtime, so things that cause subtle changes to inferred types could cause existing compiled clients to break in terrible ways if a change to the implementation caused the inferred type to change (modulo erasure.) So using this for implementation, but not for API, is a sensible guiding principle.
It is reasonable to ask "so, what about private fields and methods?" And indeed, we could well have chosen to do that. Like all design decisions, this is a tradeoff; it would enable inference to be used in more places, in exchange for more complexity in the user model. (I don't care as much about complexity in the spec or the compiler; that's our problem.) It is easier to reason about "inference for local variables yes, fields and methods no" than adding various epicyclic considerations like "but, fields and methods are OK if they are private". Drawing the line where we did also means that the compatibility consequences of changing a field or method from private to nonprivate doesn't have accidental interactions with inference.
So the short answer is, doing it this way makes the language simpler, without making the feature dramatically less useful.
Various reasons:
Visibility and type are orthogonal - one shouldn't impact the other. If private variables could be initialized with
var
, you'd had to change that when making them protected or public.Because
var
uses the right-hand side to infer the type, such private fields always needed to be initialized right away. If moving initialization into a constructor, you'd have to make the type explicit.With
var
the compiler can infer types that you can currently can't express in Java (e.g. intersection types likeComparable & Serializable
). You might of course end up relying on those specific types and when you have to stop usingvar
at some point for any reason, you might have to refactor quite a lot to keep your code working.
It’s not like it was entirely impossible to turn these variables into fields that can be inspected via Reflection. E.g., you can do
var l = new ArrayList<String>();
l.add("text");
System.out.println(l);
System.out.println(
new Object(){ { var x = l; } }.getClass().getDeclaredFields()[0].getGenericType()
);
In the current version, it just prints ArrayList
, so the actual generic type has not been stored in the class file of the anonymous inner class and it’s unlikely that this will change, as supporting this introspection is not an the actual goal. It’s also just a special case that the type is denotable like ArrayList<String>
. To illustrate a different case:
var acs = true? new StringBuilder(): CharBuffer.allocate(10);
acs.append("text");
acs.subSequence(1, 2);
System.out.println(
new Object(){ { var x = acs; } }.getClass().getDeclaredFields()[0].getGenericType()
);
The type of acs
is an intersection type of Appendable
and CharSequence
, as demonstrated by invoking a method of either interface on it, but since it is not specified whether the compiler infers #1 extends Appendable&CharSequence
or #1 extends CharSequence&Appendable
, it is unspecified whether the code will print java.lang.Appendable
or java.lang.CharSequence
.
I don’t think that this is an issue for a synthetic field, but for an explicitly declared field, it might be.
However, I doubt that the expert group considered such impacts en detail. Instead, the decision not to support field declarations (and hence skip lengthy thinking about the implications) was made right from the start, as local variables always were the intended target for that feature. The number of local variables is much higher than the number of field declarations, so reducing the boilerplate for local variable declarations has the biggest impact.
It would be a reasonable decision to allow var
for private fields (IMO). But omitting it makes the feature simpler.
Also it can be added in some future release after there is more experience with the local-only type inference, while removing a feature is much harder.
Elaborating on Nicolai's answer (specifically his #2 reason), the proposed draft of JLS 10 states that both var e;
and var g = null;
are illegal for local variables, and for good reason; it's not clear from the right-hand side (or lack thereof) which type to infer for var
.
Currently, non-final instance variables are automatically initialized depending on their type (primitives to 0
and false
, and references to null
, as I'm sure you already know). The inferred type of an instance variable would remain unclear unless it is initialized at declaration or within its respective class' constructor(s).
For that reason, I support allowing var
to be used only when the variable is both private
and final
so we can ensure that it is initialized by the time that the class is created. Though, I cannot say how difficult this would be to implement.
来源:https://stackoverflow.com/questions/49070380/local-type-inference-vs-instance