Topic 1.11
Local Variable Type Inference with var
In one line
Since Java 10 you can write var instead of a local variable's type when the initializer already makes the type obvious. The compiler works out the exact type once, at compile time, and the variable keeps that type forever: Java stays statically typed.
Think of it like this
A parcel with a clear window. Normally you write CONTENTS: SHOES on the label. If anyone can already see shoes through the window, writing the label again adds nothing. var lets you skip the label when the contents (the value on the right) make it obvious. The parcel still holds shoes and only shoes; you just didn't write the word.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Type inference
- The compiler working out a type for you from the surrounding code, instead of you writing it.
- var
- A word you write in place of a local variable's type, asking the compiler to infer it from the initializer.
- Initializer
- The value after
=in a declaration, such as10invar count = 10;. - Local variable
- A variable declared inside a method, constructor or block. Only these can use
var. - Static typing
- Every variable's type is fixed when the code is compiled.
varkeeps this: the type is inferred once, then fixed. - Diamond
- The empty
<>after a generic class name, as innew ArrayList<>(), asking the compiler to infer the type arguments. - Reserved type name
- A word that can't be used as a class name but can still be used for variables and methods.
varis one. - Target type
- The type the surrounding code expects for an expression. Lambdas need one, and
varcan't supply it.
Step by step
01Same variable, shorter line
These two lines produce exactly the same variable and exactly the same bytecode. With var, the compiler reads the right-hand side, sees an ArrayList<String>, and gives names that type.
The win grows with long generic types: Map<String, List<Integer>> scores = new HashMap<String, List<Integer>>(); repeats itself, and var scores = new HashMap<String, List<Integer>>(); says it once.
ArrayList<String> names = new ArrayList<String>(); // type written twice
var names2 = new ArrayList<String>(); // type written once, same result02What the compiler does
During compilation, javac type-checks the initializer first, then copies its type to the variable. From that moment the variable behaves exactly as if you had written the type: every later assignment and method call is checked against it.
Nothing happens at run time. The JVM never sees var; it sees an ordinary local slot of type int, String or whatever was inferred. That is why var costs nothing in performance.
03The literal decides the exact type
The inferred type is the type of the expression, nothing wider or narrower. var b = 5; gives an int, even if you meant a byte. var total = 0; followed by adding long values fails, because total is an int.
When the precise type matters, either use a suffix (0L, 0.0, 0.0f) or write the type. long total = 0; is clearer than var total = 0L; for many readers.
var a = 5; // int
var b = 5L; // long
var c = 5.0; // double
var d = 5.0f; // float
var e = 'x'; // char
var f = "x"; // String
var g = 5 / 2; // int, value 204Why var needs an initializer
var total; has nothing to infer from, so it's rejected. var name = null; is rejected too: null fits every reference type, so there's no single answer.
An array initializer {1, 2, 3} is not an expression with a type of its own; it only means something when the declared type says 'int array'. Write var nums = new int[] {1, 2, 3}; instead. A lambda such as () -> "hi" is in the same position: it needs a target type to know which interface it implements (Phase 10).
05The diamond trap
The diamond <> infers type arguments from the left-hand side. With var there is no left-hand type, so new ArrayList<>() falls back to ArrayList<Object>. The code compiles, and then happily accepts a String and an Integer in the same list, which is usually a bug waiting to happen.
Rule: with var, put the element type on the right: var names = new ArrayList<String>();. Generics are covered in Phase 8.
06var in loops
var shines in loops over collections, where the element type can be long. for (var entry : scores.entrySet()) replaces for (Map.Entry<String, Integer> entry : scores.entrySet()).
In a counting loop, for (var i = 0; i < n; i++) makes i an int, which is what you'd write anyway.
07When not to use var
var hides the type from the reader. That's fine when the right side shows it, and harmful when it doesn't: var result = process(data); forces the reader to look up process to know what result is.
Good: var reader = new BufferedReader(...), var count = 0, var entry : map.entrySet(). Better with a type: results of methods whose name doesn't reveal the type, numbers where int vs long matters, and anywhere you want to program to an interface (List<String> names = new ArrayList<>(); deliberately declares the variable as a List, while var would make it an ArrayList).
Try it yourself
- 1
Prove the type with behaviour
In the first example, add
var half = count / 3;and print it. Predict first:countis 15 at that point and both sides areint, so the answer is5. Then change it tovar half = count / 3.0;and predict again (5.0). - 2
Make var refuse
Add
var nothing;on a line of its own and compile. Then tryvar empty = null;. Read both error messages: each one says why there was nothing to infer from.terminal$ javac Main.java── expected output ──Main.java:3: error: cannot infer type for local variable nothingvar nothing;^(cannot use 'var' on variable without initializer)1 error - 3
See that the bytecode is the same
Write a class with
int a = 1;and compile it, then change it tovar a = 1;and compile again. Runjavap -c Mainboth times and compare: the instructions are identical (iconst_1,istore_1).
Code & diagrams
Expected output
count / 4 = 2
price / 4 = 2.5
big + 1 = 3000000001
letter + 1 = 66
name.length = 4
count now = 15Expected output
hello Asha
hello Ravi
Asha -> 91
Ravi -> 72
1+2+3+4 = 10Expected output
anything: [text, 42]
words: [text]With `Object point = new Object() { ... };` the line `point.x` would not compile, because `Object` has no field `x`. This trick is legal but rarely a good idea in real code.
Expected output
still legal
x + y = 7Writing `(a, b) -> a + b` does the same. The `var` form exists so you can annotate parameters, as in `(@Nonnull var a, @Nonnull var b) -> a + b`. Either all parameters use `var` or none do.
Expected output
2 + 3 = 5Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Change the type of a var variable
Write var price = 10; and later price = 12.5;.
public class Main {
public static void main(String[] args) {
var price = 10;
price = 12.5;
}
}Break #2
Use var for a field
Declare var count = 0; directly inside the class, outside any method.
public class Main {
var count = 0;
public static void main(String[] args) {
}
}Break #3
Declare two vars in one statement
Write var a = 1, b = 2;.
Myth vs fact
Myth
var makes Java dynamically typed, like JavaScript's var.
Fact
The type is inferred once at compile time and fixed forever. Assigning a value of another type is a compile error.
Myth
var is slower because the type is found at run time.
Fact
Inference happens in javac. The bytecode is identical to writing the type yourself, so there is zero run-time cost.
Myth
var is a new keyword, so old code with a variable named var breaks.
Fact
It is a reserved type name. Variables and methods named var still compile; only types named var are forbidden.
Myth
var list = new ArrayList<>(); gives a list of whatever I add later.
Fact
It gives ArrayList<Object>, decided on that line. Put the element type on the right: new ArrayList<String>().
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
varwas added by JEP 286 in Java 10; JEP 323 in Java 11 extended it to lambda parameters. JLS 14.4.1 defines the inferred type as the upward projection of the initializer's type, which removes capture variables (for example fromList<?>). - ▸
varcan hold non-denotable types, types you can't write in source: an anonymous class type (its extra members stay accessible), or an intersection type, as invar x = flag ? 1 : "s";, whose type combinesSerializable,Comparableand other interfaces common toIntegerandString. - ▸
Because the variable takes the concrete type,
var list = new ArrayList<String>();exposesArrayList-only methods such asensureCapacityandtrimToSize. Code that should depend only on an interface should declareList<String>explicitly. - ▸
IDE tooling and
javap -l(withjavac -g) show the inferred types in theLocalVariableTable; there is novaranywhere in the class file. OpenJDK's 'Local Variable Type Inference: Style Guidelines' (Stuart Marks) is the de-facto reference for when to use it.
Remember this
- 1
varworks only for local variables that are initialized on the same line:var count = 10;. The compiler looks at the type of the initializer (10is anint) and uses that exact type as if you had typedint count = 10;. This is called type inference. - 2
vardoes not make Java dynamic. The type is chosen once, at compile time, and never changes. Aftervar count = 10;, the linecount = 12.5;is still an error, becausecountis anint. The class file is identical to the one you'd get by writing the type yourself:varleaves no trace in the bytecode. - 3
The inferred type is exactly the initializer's type, which matters for literals:
var n = 5;isint(notbyteorlong),var d = 5.0;isdouble,var f = 5.0f;isfloat,var big = 5L;islong,var c = 'A';ischar. With generics,var list = new ArrayList<>();infersArrayList<Object>, because the diamond has nothing to go on; writenew ArrayList<String>()instead. - 4
Where
varis not allowed: fields, method parameters, method return types, a declaration with no initializer (var x;), an initializer ofnull, an array initializer (var a = {1, 2};), a lambda or method reference with no target type, and several variables in one statement (var a = 1, b = 2;). Each of these gives the compiler nothing exact to infer from, orvarwas deliberately kept local. - 5
Where
varis allowed and useful: ordinary locals, the index of a classicforloop, the variable of an enhancedforloop (for (var entry : map.entrySet())), resources in try-with-resources (Topic 7.6), and, since Java 11, lambda parameters ((var a, var b) -> a + b), which mainly exists so you can put annotations on them. - 6
varis a reserved type name, not a keyword. Old code that usedvaras a variable or method name still compiles (var var = "ok";is legal), but no class or interface may be namedvarany more. The style rule most teams follow, from OpenJDK's own guidelines: usevarwhen the right-hand side shows the type (new, a factory method with a clear name, a literal), and spell the type out when it would make the reader guess.
Explain it without notes
What does var do, and why is Java still statically typed when you use it?
List the places where var can't be used and give the reason for two of them.
What type does var list = new ArrayList<>(); have, and why is that a problem?
When would you choose to write the type instead of var, even though var would compile?
Practice
Rewrite this with var wherever the type stays obvious: String city = "Delhi"; int pop = 32; double area = 1484.0; and print pop / area per square km (just the raw division).
Use var to build a TreeMap<String, Integer> of three fruits and their prices (apple 120, banana 40, cherry 300), then loop with var over the entries and print each, followed by the total.
Show that var keeps the literal's exact type: declare var small = 7; and var large = 7L;, and print small * 1_000_000_000 and large * 1_000_000_000.
Trade-offs
- ↔
varremoves noise from long generic declarations and keeps the variable name at a fixed column, but it hides the type; that is a gain when the right side shows the type and a loss when it doesn't. - ↔
vargives the concrete type (ArrayList), while an explicit declaration can choose an interface (List); the interface keeps later code from depending on implementation details. - ↔
With literals,
varsilently picksintordouble; writing the type makes the numeric range visible, which matters for sums and money.
Done when you can
I can say exactly where
varis allowed and where it isn't.I can predict the inferred type of literals and
newexpressions.I know
varis resolved at compile time and leaves no trace in bytecode.I avoid the
new ArrayList<>()trap withvar.I can decide when
varhelps readability and when an explicit type is better.