Variation in local variable declaration in Java

“person typing on brown typewriter” by rawpixel on Unsplash

Early 2016 Oracle send out a survey asking the Java community about the use of type inference. Most including me responded positively and wanted something similar to the Scala’s/Kotlin’s var and val. Fast forward to 2018 and Java 10 has finally introduced its version of type inference.

Putting aside procrastination, I finally have been able to play around with the var type that got introduced into Java 10 and following are my thoughts. JEP 286 introduced a new type inference to Java. Java’s take on it is quite different from other languages that have a similar functionality.

What was the problem JEP 286 was trying to solve?

The Java language is quite verbose in nature and developers would constantly complain about it. Java has a lot of boilerplate and redundant code that made writing Java code tiresome. Plus the fact that nearly all statically typed languages have solved this obstacle a while back did not help. This time around, the more specific problem it was trying to solve was around type inference.

Java developers have always had to write the type twice even though it could easily be inferred. For example,

Declaring variables in Java

The first couple of statements are comfortable to read for any developer. However looking at the last two more complex statements with generics, it might start to get tedious and dull to write as well to read.

Java 10 aims to solve this by introducing a var type. This is accomplished by replacing all the type declaration on the left-hand side with var.

Using var

This code is now much easier to read and less tiresome to write.

Another common style of writing code are instances where a variable is declared and only used once, most often in the next line.

final File fileToRead = new File(pathToFile);
final FileReader fileReader = new FileReader(fileToRead);
final BufferedReader buffReader = new BufferedReader(fileReader);

String line;
while ((line = buffReader.readLine()) != null) {
// Do something with the line
}

There is far too much verbosity and a lot of repetition in the code. It is also uncomfortable to figure out the variable names as each of the variable types have different character count and thus the variable names are all misaligned and our eyes need hop around to find them. Let’s now modify the first three statements from the above code to use var and see how it improves the code.

final var fileToRead = new File("/tmp/dir/important-file.txt");
final var fileReader = new FileReader(fileToRead);
final var buffReader = new BufferedReader(fileReader);

As you have noticed, this is much easier to read and variables are all aligned quite nicely. (Yes, I made sure to have variables names that align properly, but don’t think anyone would complain).

In each of the above cases, the compiler can easily infer the type by examining the right-hand initialisation part. It does not add any new functionality rather adds a syntactic sugar to make writing and reading Java code a fraction easier than before. Java is still a statically typed language and introducing var does not change this fact. So declaring a variable as an integer,

var myVar = 10;  // Infers as int

And assigning a String to the variable will result in a compilation error.

myVar = "I am a String";  // Compilation error!

Now var is not a keyword instead, it is a reserved type name which basically means that if you already have a variable or a method named var, it will continue to work but we cannot have a class named var. Then again having a class named var is against the naming convention, so using var should not cause issues to existing code bases.

... ...
private String var() {      // No issues here
return "I am a String";
}
... ...
... ...
var var = var();   // This will work and the variable 'var' will be inferred as a String

Moreover, this is a compile time functionality and after compilation, the byte-code will not have the var type but instead the inferred type. So there are no performance issues.

Where can I use var?

  • This functionality can be used to initialise local variables.
  • In loops
for (var i = 0; i < 100; i++) {
...
}
for (var l : Arrays.asList(1, 2, 3, 4, 5)) {
...
}
  • try-with-resource
try (var connection = DriverManager.getConnection(url);
var statement = connection.createStatement();
var resultSet = statement.executeQuery(sql)) {

while(resultSet.next()) {
...
}
}

So what benefits have I seen?

There were few immediate benefits I noticed once I started using var in my code.

  • I found that the readability of the code had improved. When I came back to read a code after a week, I felt I could read the code faster and skimming through it was easier.

An example of improved readability I have experienced is with this simple example. The original code looked something like

final Map<Customer, List<Account>> customerMap = new HashMap<Customer, List<Account>>();

...

private boolean doesCustomerHaveAccount(final Map<Customer, List<Account>> customerMap, final String accountId) {
    final Set<Map.Entry<Customer, List<Account>>> customerSet = customerMap.entrySet();

for (Map.Entry<Customer, List<Account>> pair : customerSet) {
final List<Account> accountList = pair.getValue();
        final Stream<Account> accountStream = accountList.stream();
        final boolean accountPresent = accountStream
.map(Account::getAccountId)
.anyMatch(accountId::equals);

if (accountPresent) {
return true;
}
}

return false;
}

Updating the above code using local variable type inference the code was modified into,

final var customerMap = new HashMap<Customer, List<Account>>();

...

private boolean doesCustomerHaveAccount(final Map<Customer, List<Account>> customerMap, final String accountId) {
final var customerSet = customerMap.entrySet();

for (var pair : customerSet) {
final var accountList = pair.getValue();

final var accountStream = accountList.stream();
        final var accountPresent = accountStream
.map(Account::getAccountId)
.anyMatch(accountId::equals);

if (accountPresent) {
return true;
}
}

return false;
}

Here I noticed that my eyes weren’t running a lot and I could easily and quickly figure out what was going on in the code. Interestingly, I was not interested in knowing the type of each of the variable as my goal was to understand the functionality quickly and move on.

  • There were less boilerplate code, less verbosity and I did not have to type in the ceremonies Java forced on us.
  • I can say my hands and the keyboard to an extend were thankful as well.

Are there any drawbacks?

Despite the benefits, it is not without its problems with the way Java chose to implement the second most voted choice from the survey. It has a very limited use and a few restrictions on where var can be used.

As I have alluded in the title and elsewhere in this post, it can only be used for local variable with initialisation. JEP 286 states the following,

Enhance the Java Language to extend type inference to declarations of local variables with initializers.

This means that the var cannot be used for class/field variables, local variables that are not initialised when it is declared, catch clause, methods types or assign null. So the following code snippets will not work. These cases makes sense as it is not always easy to infer the type.

var str;                    // Compilation error!
str = "Foo";

try {
...
} catch (var exception) { // Compilation error!
...
}

private var returnFoo() {   // Compilation error
return "Foo"; // But we can infer it as String
}
var someVar = null;         // Compilation error

Lambdas and Method references also do not work.

// Compilation error 
// even though we can infer type to be Function<String, Integer>
var sInt = Integer::parseInt;
// Compilation error 
var doubleIt = a -> a * 2
var doubleIt2 = (Integer a) -> a * 2; // Still won't work
// Compilation error.
var lambdaVar = (a) -> {};

In the above statement, doubleIt could be of type Function<Integer, Integer> or Function<Integer, Number> or Function<Double, Double> etc. And for lambdaVar, the type cannot be inferred as we do not know the a’s type.

Short cut syntax to initialise arrays does not work although using the new operator works.

var iArrFails = {1, 2, 3, 4, 5}; //Compilation error
var iArrWorking = new int[5];    // This will work

Inheritance will not work as expected. If we initialise a variable to a sub class, the var will be inferred to the sub class and not the base class, so any reassignment to another sub class of the same base class or even the base class will not work.

var animal = new Dog();  // Inferred as Dog
animal = new Cat(); // Compilation error
animal = new Animal(); // Compilation error

Conclusion

After using the var reserved type for couple of weeks, I have decided to continue to use it but due to the restrictions, the actual benefit is not a lot when compared to other statically typed languages that have a similar feature.

Type inference has been in Java for a while now. My first encounter with type inference was with generics in Java 5. Java 7 added a syntactic sugar to make generic easier to work with. Java 8 took this to a new level with lambdas and now in Java 10 more type inference has been added. It does not end here, in Java 11 type inference has been expanded to allow var to be used in lambdas.

// Available from Java 11
Function<Integer, Integer> doubleIt = (var a) -> a * 2;

In essence, there is no big functionality added with the introduction of the var reserved type but rather a new syntactic sugar added to aid writing cleaner code that is easier to read as well. Hopefully, it will be expanded and improved upon further to make a bigger impact in the future releases.


The views and opinion discussed here are my own. Some of the code mentioned in this blog could be made better, but the purpose of this blog is to demonstrate the usage of var and not to make the code efficient.

References:

JEP 286

JEP 323