According to some surveys such as that of JetBrains, version 8 of Java is currently the most used by developers all over the world, despite being a 2014 release. What you are reading is the first in a series of articles titled “Going beyond Java 8”, inspired by the contents of my book “Java for Aliens”. These articles will guide the reader step by step to explore the most important features introduced starting from version 9. The aim is to make the reader aware of how important it is to move forward from Java 8, explaining the enormous advantages that the latest versions of the language offer.
In this article we will talk about the most important new feature introduced with Java 10. Officially called local variable type inference, this feature is better known as the introduction of the word var
. Despite the complicated name, it is actually quite a simple feature to use. However, several observations need to be made to see the impact that the introduction of the word var
has on other pre-existing characteristics.
Verbosity
In recent years, an important part of the evolution of the Java language has been dedicated to making the syntax more synthetic (in programming we usually prefer to say less verbose). To do this, efforts have been made to assign additional tasks to the compiler. In the case of the type inference for local variables, the compiler is able to automatically infer the type of the local variable we are declaring, allowing us to use the word var
instead of the type of the variable. For example, suppose we have the JavaBook
class, we know how to instantiate an object with the following syntax:
JavaBook obj1 = new JavaBook();
Instead, using the type inference for local variables, we can write:
var obj1 = new JavaBook();
In fact, the compiler is able to infer the type of the obj1 variable by reading its initialization, that is the right hand side (RHS) of the declaration. The immediate advantage is to be able to have a less verbose code, risking to make less mistakes and without compromising its readability.
Readability
However, the use of the word var
, could sometimes make our code less readable. In fact, in the previous example we used a so-called manifest type, because by reading the declaration of the variable obj1
, the inferred type is also evident to us programmers. Just read the right side of the statement instead of the left side.
The inference of the type, however, would also take place if, instead of clearly assigning an instance of the JavaBook
class to the variable obj1
, we assign it the return value of a method. For example, suppose you have the following method available in the same class:
public JavaBook getInstance() { return new JavaBook(); }
we could however use the type inference in this way:
var obj2 = getInstance();
In this case the code is less readable for us programmers. In fact, although the compiler automatically infers the type for obj2
, we will have to read the return type in the getInstance
method declaration to discover that it is a JavaBook
type. For this reason, we say that are using a unmanifest type. In this specific case, we’ve improved the verbosity of our code at the expense of readability, and that doesn’t seem like a good decision. We should always favor the maintenance of our programs rather than save some typing on the keyboard.
Best practice
We all know that giving meaningful names to our variables is very important in Java programming. In the case of using an unmanifest type, choosing a meaningful variable name becomes even more important. The obj2
identifier, for example, does not seem to be suitable since it does not give us information about its type. In this particular case we could instead declare the variable as follows:
var javaBook = getInstance();
In this way we could reasonably assume the type of the variable. It is therefore essential to use meaningful names for the variables that we want to declare through the word var
.
Is it a keyword?
The word var
is not a keyword, but a reserved type name. A keyword cannot be used for any type of identifier (variables, methods, modules, classes, interfaces etc.), whereas a reserved type name has fewer restrictions. In particular, in order not to impact too much on the pre-Java 10 code, it was decided that it is permissible to use the var
identifier for variables (both instance and local). It is even possible to declare a local variable like this:
var var = 0;
where the first var
is the reserved type name, while the second is the variable identifier.
It is also possible to use the var
identifier to declare a method. In fact the method:
public void var() { }
is syntactically correct.
The word var
can also be used as a package identifier without any problems:
package var; //code omitted
and also as an identifier for modules.
The only limitation that has been imposed is that it is not possible to declare a Java type (i.e. a class, an interface, an enumeration, an annotation or a record) with the var
identifier. For example, the following statement:
class var {}
would produce the following compile-time error:
error: 'var' not allowed here class var {} ^ as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations 1 error
However, due to the convention that requires type names to start with a capital letter, the possibility that the introduction of the word var
could cause damage to code written before the advent of Java 10 is very minimal.
Applicability: types
You can use the word var
with all primitive and complex types. For example, the following snippet compiles without problems:
var bool = false; // boolean type inferred var string = "Foqus";// String type inferred var character= 'J'; // char type inferred var integer = 8; // int type inferred var byteInteger = (byte)8; // byte type inferred var shortInteger = (short)8; // short type inferred var longInteger = 8L; // long type inferred var floatingPoint = 3.14F; // float type inferred var doublePrecisionfloatingPoint = 3.14; // double type inferred
As asserted before, type inference only works for local variables. Therefore, it is not applicable for instance variables, for return types of a method, for type of parameter of a method, etc.
Furthermore, the word var
cannot be used for local variables that are not initialized at declaration time, as in the following example:
var notInitialized;
which will produce the output:
error: cannot infer type for local variable notInitialized var notInitialized; ^ (cannot use 'var' on variable without initializer) 1 error
The word var
is also not usable if the variable is initialized to null
. Indeed:
var nullInitialized = null;
will print:
error: cannot infer type for local variable nullInitialized var nullInitialized = null; ^ (variable initializer is 'null') 1 error
The inference cannot be used even in the case of multiple variable declaration. For example:
var var1 = 1, var2 = 2;
will produce the following compile-time error:
error: 'var' is not allowed in a compound declaration var var1 = 1, var2 = 2; ^ 1 error
Finally, we cannot use var
as identifier to declare arrays. For example, the snippet:
var varArray[] = new int[3];
will cause the following error:
error: 'var' is not allowed as an element type of an array var varArray[] = new int[3]; ^ 1 error
In fact, as regards the inference of the type for an array, the compiler infer the array type using the left hand side of the declaration (LHS), while the word var
require to use the right part (RHS) of the declaration.
Applicability: loops
It is also possible to use the word var
as an index type in the initialization of a for
loop. For example we can write:
String [] strings = {"Antonio", "Ludwig", "Johann Sebastian", "Piotr"}; for (var i = 0; i < strings.length; i++) { System.out.println(strings[i]); }
In fact, the type will be inferred from the value in the right part of the assignment. In our case 0
is considered an int
.
Or we can use the word var
in place of the data type of the temporary variable in an enhanced for
loop (better known as a foreach loop). So the following code is valid:
int [] arr = {1,2,3,4,5,6,7,8,9}; for (var tmp : arr) { System.out.println(tmp); }
We note that in the previous example, the use of the word var
favors the evolution of the code. In fact, without modifying the loop, we can change the array to our liking. For example, the following are all valid declarations for arr
:
double [] arr = {4.8, 44.5, 100.1, 1.2, 3.0}; boolean [] arr = {true, true, false, true, true, false}; JavaBook [] arr = {new JavaBook("Il nuovo Java"), new JavaBook("Java for Aliens"), new JavaBook("Manuale di Java 9")}; JCheckBoxMenuItem []arr = {new JCheckBoxMenuItem("File"), new JCheckBoxMenuItem("Edit"), new JCheckBoxMenuItem("Help")};
The foreach loop will work with any of these declarations without having to be changed.
Applicability: switch
expressions
switch
expressionsWe can also use the word var
with a variable to which a switch
expression is assigned, a feature preview introduced with Java 12 and definitively officialized with version 14. With the switch
expression we can use the old switch
construct as a poly-expression, in the sense that it can define multiple expressions (we will soon dedicate an article from the “Going beyond Java 8” series to this construct too).
When the type to be returned by the switch
expression is known, then all the case
clauses must return values consistent with the type. This means that the following snippet is valid:
String integer = "2"; var index = switch(integer) { case "1"-> { byte b = 1; yield b; } case "2"-> { short s = 2; yield s; } case "3"-> 3; default -> -1; };
In fact, the compiler checks the right side (RHS) of the expression, or more precisely, of the expressions. Since in the three expressions a byte
, a short
and an int
are returned, obviously the int
type is inferred as the return type of the switch
expression. This is odd, since if we had used int instead of the word var
directly, the compiler would have relied on the left side of the declaration (LHS) to determine if the types returning by all case clauses are compatible.
Applicability: lambda expressions and method references
As for arrays, also for lambda expressions the compiler is designed to read the type of the variable in the left part (LHS) of the declaration, while the word var
is designed to replace a data type that can be inferred by analyzing the right part (RHS) of the declaration. This means that it is not possible to use the word var
with a local lambda expression.
For example, consider the following statement:
Runnable r = () -> System.out.println("Java 8: Anonymous function");
if we try to use var
instead of the type:
var r = () -> System.out.println("Java 8: Anonymous function");
we would get the following compile-time error:
error: cannot infer type for local variable r var r = ()->System.out.println("Java 8: Anonymous function"); ^ (lambda expression needs an explicit target-type) 1 error
Predictably, the word var
cannot be used with method references either. In fact, also for method references, is the left part (LHS) of the declaration that allow the compiler to infer the details of the declaration. With the use of var
, we would get, as usual, a compile-time error. For example, if we consider the following functional interface:
@FunctionalInterface public interface Print { void print(Object object); }
we can use it to assign it as an implementation the reference of the print
method of the System.out
object (print
is an equivalent method to println
, which does not wrap after printing):
Print print = System.out::print;
but if we try to use var
instead of the type Print
:
var print = System.out::print;
we will get the following compile-time error:
error: cannot infer type for local variable print var print = System.out::print; ^ (method reference needs an explicit target-type) 1 error
Applicability: local anonymous classes
We know that an anonymous class is always declared with the purpose of overriding one or more methods of the type it extends. Now let’s consider the following code which declares an anonymous class that extends the Object
class, but instead of overriding one of its methods, it defines a new one:
public class VarAnonymousTest { public static void main(String args[]) { Object testObject = new Object() { String name ="This can be used with var!"; void test(String test){ System.out.println(test); } }; testObject.test("TEST!");//compile-time error } }
In the last line we try to call the test
method using the reference testObject
which is of type Object
, obtaining an error during compilation:
VarAnonymousTest.java:9: error: cannot find symbol testObject.test("TEST!");//compile-time error ^ symbol: method test(String) location: variable testObject of type Object 1 error
In fact, an Object
type reference cannot invoke a method defined in another class. In order to invoke this method, we would need a reference of the type of the anonymous class. But a reference of the anonymous class cannot exist, just because anonymous classes do not have a name. However, with the introduction of the word var
, this limit can be overcome!
Let’s rewrite the previous class using the word var
:
public class VarAnonymousTest { public static void main(String args[]) { var testObject = new Object() { String name ="This can be used with var!"; void test(String test){ System.out.println(test); } }; testObject.test(testObject.name); //it works! } }
we can see that we could use both the name variable and the test method, which we defined from scratch in the local anonymous class. In this case, therefore, the introduction of the word var
, in addition to representing a tool to reduce the verbosity of Java, has allowed us to overcome a limit of the language.
Conclusions
In this article we have introduced the word var
, and we have seen how we can use it instead of the type of local variables. The obvious advantage of using var
is to streamline the code, but it is not the only one. For example, we have seen that it supports software evolution, and that surprisingly, it makes local anonymous classes more useful and powerful, allowing us to take advantage of new solutions that in the past we could not exploit. The local variables type inference is therefore another reason to move forward from Java 8.
Author Notes
Even ignoring the increased security offered by the latest versions of the JDK, there are plenty of reasons to upgrade your knowledge of Java, or at least your own Java runtime installations. My book “Java for Aliens“, which inspired the ” Going beyond Java 8″ series, contains all the information you need to learn Java from scratch, and uses a well-tested teaching method that has been perfected over 20 years of experience, which makes learning simple and exciting. It is also structured to deepen the topics and have superior knowledge that can make a difference to your career.
This article is inspired by various paragraphs of chapters 3, 4, 11 and 17 of the book “Java for Aliens”. Actually, the topic is explored further in other chapters where the impact of this feature is evaluated, for example on polymorphism.
For more information visit https://www.javaforaliens.com.