You are currently viewing Local Variable Type Inference (var)

Local Variable Type Inference (var)

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

We 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.

Leave a Reply