Support
Blog chevron_right Java

Local Variable Type Inference in Java: Friend or Foe?

Local Variable Type Inference in Java: Friend or Foe?

Summary

Local variable type inference helps simplify Java code. Introduced in JDK 10, LVTI seems an obvious way to produce more elegant code. But in some situations, it can actually make code harder to understand, so developers must be careful when using LVTI.

In this post you will learn:

  • A local variable’s scope defines when it is valid as a reference. The variable’s scope starts where the variable is defined to the end of the block of code in which it is defined
  • LVTI allows developers to use var and reduce boilerplate code without sacrificing readability
  • Java code become harder to discern when no type information is stated explicitly
  • Most developers build applications with IDEs, but a basic design principle of LVTI is that code readability shouldn’t depend on IDEs

Java remains one of the most popular programming languages in the world, 30 years after its release. However, it is not without its critics, many of whom complain that the syntax is too verbose and even simple tasks require excessive boilerplate code. In its more recent history, OpenJDK architects have expended considerable effort to remove some of these “rough edges.” Project Amber has delivered numerous small changes to the language, making developers’ lives easier without compromising the readability that lies at the heart of Java. 

One such change was local variable type inference (LVTI), introduced in JDK 10. At first glance, this would seem like an obvious way to simplify Java code and is used in many other popular languages like Kotlin, Rust and Swift. But as we’ll see, in some situations, type inference can make code harder to understand, and we should avoid these use cases. 

What is local variable type inference?

Local variables in Java only maintain a value during a specific time in a method call. A local variable’s scope defines when it is valid as a reference.

LVTI in Practice
The variable’s scope starts where the variable is defined to the end of the block of code in which it is defined.

This could be an entire method, a block of code within a method (delimited by braces and rarely used), or within a block formed by a loop (for or while).

Important: Local variables are subject to definite assignment; they must have a value assigned to them. Unlike instance variables for classes, they have no default values. 

Here’s a simple example of a local variable definition: 

ArrayList<String> names = new ArrayList<String>(); 

You can immediately see why people might complain about Java’s verbosity, as the one line of code repeats the type, ArrayList<String> twice.

ArrayList<String> names = new ArrayList<String>();

Back in Java SE 7, Project Coin reduced this verbosity slightly by introducing the diamond operator. Our line of code can be simplified to this: 

ArrayList<String> names = new ArrayList<>(); 
Pro Tip: Technically, this is not an operator, but a way of signaling to the compiler that it should infer the generic type parameter of the ArrayList we are instantiating. Since the variable names is an ArrayList of type String, the compiler can easily infer that the generic type is String and use that when instantiating our object. 

Local variable type inference moves to the next level

LVTI takes this to the next logical level by allowing us to use var where we define the type of a local variable (so long as it is immediately assigned a value). 

Our line now becomes: 

var names = new ArrayList<String>(); 

Because we are instantiating a concrete object of type ArrayList with a generic type parameter of String, the compiler can infer that the variable names must be of that type, so we no longer need to state it explicitly (and repeat the type information). 

This is good because we reduce our boilerplate code without sacrificing readability. We can see the type of names just as easily as the compiler.

Pro Tip: We must return the generic type to the ArrayList definition, rather than using the diamond operator. We can still use the diamond operator, but the compiler would then infer the generic type as Object, since that is the only valid option. Be careful of this if you are refactoring code to use LVTI.

Not all the uses of var are quite so beneficial. Let’s continue with our names reference and assume that we have a lot of code after the declaration (maybe several pages), and that then we need to create an explicit Stream from our List. We could do this using var, thus: 

var s = names.stream(); 

How does LVTI perform when no type information is stated explicitly?

Now things become harder to discern because there is no type information stated explicitly in this line. The compiler knows the precise type of names, so it can infer that the variable s will be of type Stream<String>.

Pro Tip: For us, as developers, this is not obvious. We would need to remember the type of names to recognize what type s will be. To be fair, we would most likely be using an IDE to develop this code, so we can easily have the tool show us the type of s. However, one of the basic design principles of LVTI was “Code readability shouldn’t depend on IDEs.”

Another lengthy discussion around LVTI was whether there should be a distinction for final variables by using val instead of var. The readability argument won, so we must be more verbose in Java than languages like Kotlin and use final var instead of val.

Start using var in your code

As you can see, the introduction of local-variable type inference to Java provides benefits to developers. It enables them to simplify code where variables are declared and assigned an instantiated object reference on the same line. Less boilerplate code without sacrificing readability is always a good thing. However, developers should think carefully when using var outside of this situation to ensure that it delivers better code. 

Why not start using var in your code today? 

prime-cta-banner

We Love to Talk About Java

Ask any questions about Java!