Java Local Variable Type Inference

Java 10 Local Variable Type Inference
Java 10 – Local variable type inference

Introduction

Once written, a code block is definitely read more than it’s modified. Given this fact, it’s important to leave it as readable as it can be for future readers. Therefore, the Java team introduced a new feature called “Local Variable Type Inference” in Java 10 to help the developers further with this fact.

The feature is introduced with JEP 286. Let’s take a look at the excerpt from the goal section of the JEP.

Goals
We seek to improve the developer experience by reducing the ceremony associated with writing Java code, while maintaining Java’s commitment to static type safety, by allowing developers to elide the often-unnecessary manifest declaration of local variable types

JEP 286: Local-Variable Type Inference

As it is noted here, the primary purpose of this feature is to contribute to language’s intrinsic readability and we are going to take look at it in this article.

History of Type Inference In Java

To start with, let’s look at the history of type inference in Java. Type inference was introduced to java initially with version 5. This introduction enabled us to omit static type declaration while using generics. For instance, consider the following expression:

Set<Integer> integerSet = Collections.<Integer>emptySet()

since java 5, it’s possible to write this expression in a more simpler way like the following:

Set<Integer> integerSet = Collections.emptySet();

Java compiler infers the generic type of the set returned by the emptySet() method for you.

After this introduction, the Java team expanded the scope of type inference in Java 7 further by introducing the diamond operator. For example, instead of:

Map<Order, List<Product>> productsByOrder = new HashMap<Order, List<Product>>();

You can write:

Map<Order, List<Product>> productsByOrder = new HashMap<>();

Java 8 extended this further to infer the types in Lambda expressions:

Predicate<String> isEmptyString = (String x) -> x != null && x.length > 0;
//OR with inference
Predicate<String> isEmptyString = x -> x != null && x.length > 0;


Local Variable Type Inference

In order to aid readability in certain cases, java 10 introduced a new feature called “local variable type inference“. By using the word “var” in local contexts, we can now omit the type manifestation while declaring a local variable. Java compiler infers the type of the variable using the assignment part of the declaration.

Let’s look through some examples.

Available only for local variables

Local type inference is only supported for local variables. Therefore, It can not be used for method arguments or return types, and member fields.

void someMethod() {
		Map<String, List<Order>> orderMap = new HashMap<>();
}

//Can now be written as

void someMethod() {
		var orderMap = new HashMap<String, List<Order>>();
}

You can use it with a static type initializer. Meaning, you can use it with a constructor or a method that returns a static type.

var order = new Order(); // order type is inferred

public List<Order> getCustomerOrders(String customerId){...}

var orderList = getCustomerOrders("1234"); //List of orders is inferred

Furthermore, it can be used as the iteration variable of enhanced for loop or in a try with statement.

var stringArray = new String[11];
Arrays.fill(new String[11], 0, 10, "String Array");

for(var stringInArray: stringArray) {
    System.out.println(stringInArray);
}

try (var bufferedReader =
                   new BufferedReader(new FileReader(path))) {
        return bufferedReader.readLine();
    }

Has Java become a dynamically typed language now?

No, Java is still a statically typed language and the variables declared using local type inference are inferred at compile-time, not in runtime.

var counter = 13;
counter = "13";
|  Error:
|  incompatible types: java.lang.String cannot be converted to int
|  counter = "13"

Is var a reserved keyword now?

No it’s not a reserved keyword. This means the following:

  • It’s possbile to use “var” as a method name.
  • It’s possible to use “var” as variable or field name.
  • Finally, using “var” as a package name is also possible.
//var as a method name is allowed
public void var() {
      System.out.println("Hello var!");
  }

public void someMethod() {
  //var as a variable name is allowed
  var var = 3;
}

public class AClass {
 //var as field name is allowed
  public int var; 
}

//package name is allowed
package var;

On the other hand, you can not use it as a type name.

public class var {

}
//Build output
java: 'var' not allowed here
  as of release 10, 'var' is a restricted type name and cannot
 be used for type declarations

How about the use of local variable inference with polymorphic types?

Assume that we have an interface(or a parent class) Mammal which is implemented by two other types Leopard and Hippopotamus . For example, when we use a statement like the one below:

var mammal = new Leopard();

, does mammal have the type Leopard or Mammal ? As an illustration, consider the following statement:

var mammal = new Leopard();
mammal = new Hippopotamus();

Will this work?
No, the local variable assignment in the above snippet won’t work this time. That is to say, local variable inference does not play along with the polymorphic behavior.

Local Variable Type Inference Usage and Coding Style Recommendations

Although language features like local variable type inference are there to ease readability of the code – as well as to ease writing the code-, misuse or unconscious use of such features can lead to the complete opposite. Therefore, paying special attention to the style guides and good practice recommendations is extremely helpful to avoid such situations.

Luckily, Java.net has a dedicated “style guides and recommendations” page for this feature. Even though we are going to provide some caveats and summarize some important pieces from these recommendations. It’s particularly important to emphasize that it’s worth it to visit this page.

Let’s have a look at these recommendations and caveats now.

Pay special attention to minimising the scope of local variables

It is a good practice to reduce the scope of local variables in general. This becomes particularly important when using var. For instance, consider the following snippet from java.net’s guide page:

var items = new ArrayList<Item>(...);
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...

Assume that the order of items in the list matters for further processing of the list in this code snippet. Given this requirement, what happens if there is a new request that requires the removal of duplicate elements? A developer may achieve this goal by converting this list to a HashSet:

var items = new HashSet<Item>(...);
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...

Since the sets do not have an explicit iteration order, this code now has a bug. Nevertheless, the developer will probably fix this error when he/she makes the change. The reason is that the scope of the local variable is limited enough to make this bug cognisable at first sight.

Now assume that this variable is used in a bloated method:

var items = new HashSet<Item>(...);
items.add(MUST_BE_PROCESSED_LAST);
//...
//...
//...hundreds of lines of code
//...
//...
for (var item : items) ...

The effect of changing the list is no longer easily recognisable in this case, because the list is processed far away from the declaration. The code is now prone to stay in this buggy state longer. Therefore, it’s good practice to keep local variable scopes as limited as possible and to be cautious using var if simply using it hides the necessary information.

Use var to break up chained or nested expressions

Consider the following code block which creates a flight by destination map entry of which contains the maximum-duration flight for a specific destination.

Map<String, Flight> flightsByDestMaxDurMap = 
  flightList.stream()
     .collect(groupingBy(
        Flight::getDestination,
        collectingAndThen(
           maxBy(
             Comparator.comparing(Flight::getDuration)), 
             Optional::get)));

The intention is hardly readable from this code-block, because of the nested and chained expressions in stream processing. A developer can alternatively write this with var as follows:

var maxByDuration = maxBy(Comparator.comparing(Flight::getDuration));

var maxDurationFlightSelection = collectingAndThen(maxByDuration, Optional::get);

var flightsByDestMaxDurMap = flightList
       .stream()
       .collect(
         groupingBy(
          Flight::getDestination,maxDurationFlightSelection));

Of course, it’s justifiable to use the first approach in this case. Nevertheless, in cases where we face more complex and long chained/nested expressions, breaking up the expression using var can help a lot in terms of readability.

Approaching the conclusion, it’s worth mentioning that although we have not covered all of the recommendations stated in the “Local Variable Type Inference: Style Guidelines” page of java.net, they are still important and valuable. It’s definitely worth taking a look at this page.

Conclusion

We took a look at the local type inference feature of Java 10 and some recommendations regarding the local type inference feature. This new feature can help us make our code more readable as long as we stick with the style guides and recommendations that come along with the feature.

Happy coding!