Java 10: Local Variable Type Inference

Filed Under: Java
Java 10 Var

In this article, we would take a deep dive at the new feature of Local-Variable Type Inference introduced in Java 10. We will go through the scope and limitations of using the local variable type inference.

This feature was proposed as part of JEP (JDK Enhancement Proposal): 286. The proposal was for enhancing the language to support the type inference to local variable declaration and initialization.

For a complete overview of Java 10 release, go through Java 10 Features.

1. Java 10: Local Variable Type Inference

With Java 10, you can use var for local variables instead of a typed name (Manifest Type). This is done by a new feature which is called Local Variable Type Inference.

But first, What is Type Inference?

Type inference is Java compiler’s ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. Type Inference is not to Java programming.

For local variable declarations with initializer, we can now use a reserved type name “var” instead of a manifest type. Let’s look through a few examples.


var list = new ArrayList<String>(); // infers ArrayList<String>
var stream = list.stream();         // infers Stream<String>

Manifest Type: Explicit identification of type for each variable being declared is called as Manifest Typing. For example, If a variable “actors” is going to store a List of Actors, then its type List<Actor> is the manifest type and its must be declared (as mentioned below) prior to Java 10:

 
List<Actor> actors =  List.of(new Actor()); // Pre Java 10 
var actors = List.of(new Actor()); // Java 10 onwards 

2. How does Local Variable Type Inference work?

Parsing a var statement, the compiler looks at the right-hand side of the declaration, aka initializer, and it infers the type from the right-hand side (RHS) expression.

Ok fine enough, does this mean that now Java is a dynamically typed language? Not really, it’s still a statically typed language. Let’s take a code snippet for reading a file.


private static void readFile() throws IOException {
	var fileName = "Sample.txt";
	var line = "";
	var fileReader = new FileReader(fileName);
	var bufferedReader = new BufferedReader(fileReader);
	while ((line = bufferedReader.readLine()) != null) {
		System.out.println(line);
	}
	bufferedReader.close();
}

Now, let’s look at the decompiled code taken from IntelliJ IDEA decompiler.


private static void readFile() throws IOException {
	String fileName = "Sample.txt";
	String line = "";
	FileReader fileReader = new FileReader(fileName);
	BufferedReader bufferedReader = new BufferedReader(fileReader);
	while ((line = bufferedReader.readLine()) != null) {
		System.out.println(line);
	}
	bufferedReader.close();
}	

Here the compiler properly infers the type of the variable from the right-hand side expression and adds that to the bytecode.

3. var is a reserved type name

var is not a keyword, It’s a reserved type name. What does it mean?

  • We can create a variable named “var”.
    
    var var = 5; // syntactically correct
    // var is the name of the variable
    
  • “var” as a method name is allowed.
    
    public static void var() { // syntactically correct 
    }
    
  • “var” as a package name is allowed.
    
    package var; // syntactically correct
    
  • “var” cannot be used as the name of a class or interface.
    
    class var{ } // Compile Error
    LocalTypeInference.java:45: 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
    
    interface var{ } // Compile Error
    

4. Local Variable Type Inference Usage Scenarios

Local type inference can be used only in the following scenarios:

  • Limited only to Local Variable with initializer
  • Indexes of enhanced for loop or indexes
  • Local declared in for loop

Let’s walk through the examples for these scenarios:


var numbers = List.of(1, 2, 3, 4, 5); // inferred value ArrayList<String>
// Index of Enhanced For Loop
for (var number : numbers) {
	System.out.println(number);
}
// Local variable declared in a loop
for (var i = 0; i < numbers.size(); i++) {
	System.out.println(numbers.get(i));
}

5. Local Variable Type Inference Limitations

There are certain limitations of using var, let’s take a look at some of them.

  1. Cannot use ‘var’ on variables without initializer

    If there’s no initializer then the compiler will not be able to infer the type.

    
    var x;
    LocalTypeInference.java:37: error: cannot infer type for local variable x
                    var x;
                        ^
      (cannot use 'var' on variable without initializer)
    1 error
    
  2. Cannot be used for multiple variable definition
    
    var x = 5, y = 10;
    LocalTypeInference.java:41: error: 'var' is not allowed in a compound declaration
                    var x = 5, y = 10;
                        ^
    1 error
    
  3. Null cannot be used as an initializer for var

    Null is not a type and hence the compiler cannot infer the type of the RHS expression.

    
    var author = null; // Null cannot be inferred to a type 
    LocalTypeInference.java:47: error: cannot infer type for local variable author
                    var author = null;
                        ^
      (variable initializer is 'null')
    1 error
    
  4. Cannot have extra array dimension brackets
    
    var actorArr[] = new Actor[10];
    LocalTypeInference.java:52: error: 'var' is not allowed as an element type of an array
                    var actorArr[] = new Actor[10];
                        ^
    1 error
    
  5. Poly expressions that have lambdas, method references, and array initializers, will trigger an error

    For the type inference of Lambda expressions, Method inference and the Array initializers, compiler relies on the left hand side expression or the argument definition of the method where the expression is passed while var uses RHS, this would form a cyclic inference and hence the compiler generates a compile time error.

    
    var min = (a, b) -> a < b ? a : b;
    LocalTypeInference.java:59: error: cannot infer type for local variable min
                    var min = (a, b) -> a < b ? a : b;
                        ^
      (lambda expression needs an explicit target-type)
    1 error
    
    		 
    var minimum = Math::min;
    LocalTypeInference.java:65: error: cannot infer type for local variable minimum
                     var minimum = Math::min;
                         ^
      (method reference needs an explicit target-type)
    1 error
    
    
    var nums = {1,2,3,4,5};
    LocalTypeInference.java:71: error: cannot infer type for local variable nums
                    var nums = {1,2,3,4,5};
                        ^
      (array initializer needs an explicit target-type)
    1 error
    

6. Generics with Local Variable Type Inference

Java has type inference for Generics and to top of it, it also has to do Type Erasure for any generics statement. There are some edge cases which should be understood when using local type reference with Generics.

Type Erasure: To implement generics, the Java compiler applies type erasure to, replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.

Let’s go through some use case for var using generics:


var map1 = new HashMap(); // Inferred as HashMap
var map2 = new HashMap<>(); // Inferred as HashMap<Object, Object>

map1 – Compiler infers the map as HashMap without any generic type.

map2 – The diamond operator relies on the LHS for the type inference, where the compiler cannot infer the LHS and hence it infers map2 to have upper bound or supertype to which the HashMap can be denoted to. This leads to map2 being inferred as HashMap.

7. Anonymous Class Types

Anonymous class types cannot be named, but they’re easily understood – they’re just classes. Allowing variables to have anonymous class types introduces useful shorthand for declaring a singleton instance of a local class. Let’s look at an example:


var runnable = new Runnable() {
	@Override
	public void run() {
		var numbers = List.of(5, 4, 3, 2, 1);
		for (var number : numbers) {
			System.out.println(number);
		}
	}
};
runThread(runnable);

8. Non Denotable Types

An expression that cannot be inferred to a specific type is known as Non Denotable Type. Such type can occur for a capture variable type, intersection type, or anonymous class type. Let’s understand how a Non Denotable Type can be used for local variable type inference:


var map3 = new HashMap<>() { // anonymous class
	int someVar;
};

Here, when the diamond operator is used with anonymous class type, the compiler cannot infer the RHS expression to any specific type. This leads to a formation of non-denotable Type.

Firstly, the compiler will get denotable type by using the supertype for HashMap<>, which is HashMap<Object, Object>.

Secondly, the anonymous class extension is applied. Finally, this becomes a Non-denotable type which gets assigned to map3.

A special case of Non-Denotable type which was not possible to create earlier in Java can now be created. Anonymously extending an Object class and adding attributes within it creates a POJO like a class which can be assigned to a variable to hold context. This can be very useful in using a dynamically created object which can have a structure within a temporary context. Let’s see an example:


// Special Case Non-Denotable Type
var person = new Object() {
	class Name {
		String firstName;
		String lastName;
		public Name(String firstName, String lastName) {
			super();
			this.firstName = firstName;
			this.lastName = lastName;
		}
		public String getFirstName() {
			return firstName;
		}
		public void setFirstName(String firstName) {
			this.firstName = firstName;
		}
	}
	Name name;
	Actor actor;
	public String displayName() {
		return name.getFirstName() + " " + name.lastName;
	}
};
person.name = person.new Name("Rakesh", "Kumar");
System.out.println(person.displayName());

9. Some Fun Facts for choosing var for Local Variable Type Inference

There was a survey for the list of keywords to choose from, for the local type inference. Below is the list of syntactic options provided to community users:

  • var x = expr only (like C#)
  • var, plus val for immutable locals (like Scala, Kotlin)
  • var, plus let for immutable locals (like Swift)
  • auto x = expr (like C++)
  • const x = expr (already a reserved word)
  • final x = expr (already a reserved word)
  • let x = expr
  • def x = expr (like Groovy)
  • x := expr (like Go)

Results of the Survey:
java local variable type inference
Response of choices by percentage:
java var variable

Rationale for using the 2nd best Choice (var)

  • Even though var was the 2nd best choice, people were fine with it and almost no one hated it outright. Whereas this was not the case for the other options.
  • C# experience. C# community had found the keyword to be reasonable for a Java-like language.
  • Some readers found that var/val were so similar that they could mostly ignore the difference and it would be annoying to use the different keywords for immutable and mutable variables.
  • Majority of local variables are effectively final and punishing immutability with another ceremony is not what the intent of the JEP was.

10. Benefits of Local Variable Type Inference

  • It improves the developer experience
  • It reduces code ceremony
  • It reduces boiler plate code
  • Increases code clarity

11. Conclusion

In this article we went through Local Type Inference and why var was chosen as a syntactic option. As usual you can check the complete code at github here.

Comments

  1. techlover says:

    Great & well elaborated article. Many thanks!!

  2. Bikram Bhandari says:

    Well written with depth coverage. Liked the way you covered different scenarios.

  3. Damith says:

    Very well written with examples. Thank you.

  4. SG says:

    Great article , very well explained Rakesh !!!

  5. Amit says:

    Comprehensive and effortlessly understandable article. Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages