Local Variable Type inference in Java 10
--
Local variables are the most essential tool in Java. They let methods to compute significant results by reasonably storing provisional values. Unlike a field or instance variable, a local variable is declared, initialized, and used in the same block it was declared. The name and initializer of a local variable are often more significant to the developers than its type. Usually, the name and initializer bring just as much information as the type. Let’s look at the example below:
Shape s = new Shape();
Looking at the right-hand side of the equal sign only, we will immediately conclude that s
is of type Shape
. This code is pretty simple and straight forward; the type Shape
has only 5 characters. The problem becomes visible when we have to declare local variables type that looks like the following:
Map<Integer, List<Shape>> valMap = new HashMap<Integer, List<String>>();
Imagine we have to write a few more variables with a type that is similar to the above code, then we will realise what goodies the JavaScript, Scala, Go, Kotlin developers were blessed with.
Java 10
var
keyword to the rescue!
Local variable type inference was introduced in Java 10. Though type inference was improved a lot in Java 8 with the introduction of lambda expressions, method references, and Streams, local variables still needed to be declared with proper types — but that’s now gone.
The role of var
in a local variable declaration is to represent the type so that its type can be inferred from its initialization:
var s = new Shape();
This feature is built under JEP 286
: Local-Variable Type Inference, and authored by none other than Brian Goetz, author of Java Concurrency in Practice, one of the most popular books for Java developers
Using var
can make code brief without sacrificing readability, and in some cases, it can improve readability by removing redundancy. This does not make Java dynamically typed like in JavaScript. With var
keyword, Java is still a statically typed language only that the compiler will infer the type of the variable at compilation time, using type obtained from the variable’s initializer. The inferred type is then used as the type of the variable. Naturally, this is the same as the type you would have written explicitly, so a variable declared with var
works exactly as if we had written the type plainly.
Where can we use
var
keyword
var
can be used for declaring local variables, including index variables of for-loops and resource variables of the try-with-resources statement but cannot be used for fields, method parameters, and method return types because types in these locations appear explicitly in class files and in Javadoc specifications. With type inference, it’s quite easy for a change to an initializer to cause the variable’s inferred type to change.
var
can only be used when there is an initializer i.e. initializer is required on the right-hand side of the assignment. We could have chosen to infer the type from the assignments to the variable, but that would have made the feature considerably complex, and it could potentially lead to misleading or hard-to-diagnose errors. In order to keep things simple, var
is defined so that only local information is used for type inference. The following code will throw a compiler error at compilation time:
var age;
....
age = 24;
var
cannot be used where the right-hand side is null
or returns null
because the null
literal denotes a value of a special null
type (JLS 4.1)
that is the subtype of all reference types in Java. The only value of the null
type is null
itself.
Examples of using
var
var str = "Java 10"; // infers String
str
infers type String
var stream = list.stream(); // infers Stream<String>
stream
infers type Stream<String>
var list = new ArrayList<String>(); // infers ArrayList<String>
list
infers type ArrayList<String>
var list = List.of(1, 2.0, "3");
list
will be inferred into List<? extends Serializable & Comparable<..>>
var bos = new ByteArrayOutputStream();
The above code is equivalent to ByteArrayOutputStream bos = new ByteArrayOutputStream();
ByteArrayOutputStream repeats twice. One of it has been eliminated by using the var
feature of Java 10.
We can do similar things while using try-with-resource statements in Java. Consider the following code:
try (Stream<Student> data = dbconn.executeQuery(sql)) {
return data.map(...)
.filter(...)
.findAny();
}
which is equivalent to
try (var data = dbconn.executeQuery(query)) {
return data.map(...)
.filter(...)
.findAny();
}
Can also be used in a loop
for(var s : arrList){
....
}for(var i=0; i<str.length(); i++){
var ch = str.getCharAt(i);
}
That’s some of the examples of using keyword var
in Java 10, an interesting Java 10 feature that allows us to declare local variables without declaring their type.