var
local type inference: what it infers, and where it isn't allowed.
- chapter
- chapter 02.13
- reading time
- 7 min read
var lets the compiler infer a local variable's type from its initializer, so you don't write it twice. the type is still decided at compile time and never changes: java stays statically typed. it only works for local variables that have an initializer, never for fields, parameters or return types.
the picture
Map<String, List<Integer>> scores = new HashMap<String, List<Integer>>();that line says the same thing twice. you know how filling in a form asks for your date of birth and then your age? kinda redundant. var is java noticing the right-hand side already says what the type is (JEP 286, java 10):
var scores = new HashMap<String, List<Integer>>();the compiler reads the initializer, works out its type, and writes that type into the variable exactly as if you had typed it. it happens entirely at compile time.
hover a stage.
what it infers
the inferred type is the exact static type of the expression on the right. usually that's what you'd have written. sometimes it isn't.
the exact class on the right, ArrayList, not the List interface you might have written by hand.
/vars printed for that exact declaration. the amber ones surprise people; the red ones are traps.two to remember. var list = new ArrayList<String>() gives you an ArrayList, not a List: fine for a local, but it's a different type than you'd usually declare. and var empty = new ArrayList<>() gives you ArrayList<Object>, because the diamond had nothing to infer from. pair var with an explicit type argument, never with an empty diamond.
void main() {
var names = new ArrayList<String>();
names.add("ada");
var anon = new Object() { int hits = 3; };
IO.println(names.get(0).toUpperCase() + " " + anon.hits);
}ADA 3
names.get(0) is a String (so toUpperCase compiles), and anon.hits works because var kept the anonymous class's own type, one you couldn't have written by hand.
it's not dynamic typing
var is not any, and it's not javascript's let. once inferred, the type is locked:
var x = 5;
x = "five";
IO.println(x);where it's allowed
only where there's a local variable and an initializer to infer from. everywhere else, the type is part of a contract (fields, parameters, return types) or there's nothing to look at.
there's nothing to infer from: a parameter has no initializer, its value comes from callers.
lambda parameters joined in java 11 (JEP 323). (var x, var y) -> x + y means exactly the same as (x, y) -> x + y. the reason it exists: you can put an annotation on a var parameter, like (@NonNull var x, var y) -> ..., which you can't do with a bare name.
when to use it
var is about readability. the rule of thumb from the OpenJDK style notes boils down to: use it when the reader loses nothing.
var result = service.process(order);
var x = compute();
for (var e : lookup()) { ... }now the reader has to go find process to know what result even is.
var orders = new ArrayList<Order>();
var path = Path.of("data", "orders.csv");
var count = 0;
try (var in = Files.newBufferedReader(path)) { ... }the constructor, factory method or literal already names the type.
good variable names matter more with var, not less. var orders tells you more than List<Order> list ever did.
why you can still name a variable var+
var isn't a keyword. it's a reserved type name: it means something special only where a type is expected. so var var = "var"; compiles and prints var, and old code with variables called var kept working in java 10. what you can't do anymore is name a class var (error: 'var' not allowed here), since that would make var x = ... ambiguous. yield, record, sealed and permits were added later in the same careful way (special only where they mean something), for the same reason: never break existing code.
varinfers a local variable's type from its initializer, at compile time.- the type is fixed.
var x = 5; x = "five";doesn't compile. - it infers the exact type on the right:
ArrayList<String>, notList<String>. an empty diamond givesObject. - locals only, with an initializer. no fields, parameters, return types,
null, lambdas or bare{1, 2}. - use it when the right side already says the type. otherwise, write it out.