Java Local Variable Type Inference

Java SE 10 introduced type inference for local variables. Previously, all local variable declarations required an explicit (manifest) type on the left-hand side. With type inference, the explicit type can be replaced by the reserved type name var for local variable declarations that have initializers. The type of the variable is inferred from the type of the initializer.

There is a certain amount of controversy over this feature. Some welcome the concision it enables; others fear that it deprives readers of important type information, impairing readability. And both groups are right. It can make code more readable by eliminating redundant information, and it can also make code less readable by eliding useful information. Another group worries that it will be overused, resulting in more bad Java code being written. This is also true, but it’s also likely to result in more good Java code being written. Like all features, it must be used with judgment. There’s no blanket rule for when it should and shouldn’t be used.

Local variable declarations do not exist in isolation; the surrounding code can affect or even overwhelm the effects of using var in Java. The goal of this document is to examine the impact that surrounding code has on var declarations, to explain some of the tradeoffs, and to provide guidelines for the effective use of var.

Java Local Variable Type Inference​

Local variable declarations can make code more java code readable by eliminating redundant information. However, it can also make code less readable by omitting useful information. Consequently, use this feature with judgment; no strict rule exists about when it should and shouldn’t be used.

Local variable declarations don’t exist in isolation; the surrounding code can affect or even overwhelm the effects of var declarations. Local Variable Type Inference: Style Guidelines examines the impact that surrounding code has on var declarations, explains tradeoffs between explicit and implicit type declarations, and provides guidelines for the effective use of var declarations.

Goals

It improves the Java 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. This Java feature would allow, for example, declarations such as:

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

This treatment would be restricted to java local variables with initializers, indexes in the enhanced for-loop, and locals declared in a traditional for-loop; it would not be available for method formals, constructor formals, method return types, fields, catch formals, or any other kind of variable declaration.

In JDK 10 and later, you can declare local variables with non-null initializers with the var identifier, which can help you write code that’s easier to read.

Consider the following example, which seems redundant and is hard to read:

				
					URL url = new URL("https://techshitanshu.com/"); 
URLConnection conn = url.openConnection(); 
Reader reader = new BufferedReader(
    new InputStreamReader(conn.getInputStream()));
				
			

You can rewrite this example by declaring the local variables with the var identifier. The type of the variables are inferred from the context:

				
					var url = new URL("https://techshitanshu.com/"); 
var conn = url.openConnection(); 
var reader = new BufferedReader(
    new InputStreamReader(conn.getInputStream()));
				
			

var is a reserved type name, not a keyword, which means that existing code that uses var as a variable, method, or package name is not affected. However, code that uses var as a class or interface name is affected and the class or interface needs to be renamed.

var can be used for the following types of variables:

  • Local variable declarations with initializers:

				
					var list = new ArrayList<String>();    // infers ArrayList<String>
var stream = list.stream();            // infers Stream<String>
var path = Paths.get(fileName);        // infers Path
var bytes = Files.readAllBytes(path);  // infers bytes[]

				
			
  • Index variables declared in traditional for loops:
				
					for (var counter = 0; counter < 10; counter++)  {...}   // infers int
				
			
  • Enhanced for-loop indexes:
				
					List<String> myList = Arrays.asList("a", "b", "c");
for (var element : myList) {...}  // infers String
				
			
  • try-with-resources variable:
				
					try (var input = 
     new FileInputStream("validation.txt")) {...}   // infers FileInputStream

				
			
  • Formal parameter declarations of implicitly typed lambda expressions: A lambda expression whose formal parameters have inferred types is implicitly typed:
				
					BiFunction<Integer, Integer, Integer> = (a, b) -> a + b;
				
			
  • In JDK 11 and later, you can declare each formal parameter of an implicitly typed lambda expression with the var identifier:
				
					(var a, var b) -> a + b;
				
			
  • As a result, the syntax of a formal parameter declaration in an implicitly typed Java lambda expression is consistent with the syntax of a local variable declaration; applying the var identifier to each formal parameter in an implicitly typed lambda expression has the same effect as not using var at all.

    You cannot mix inferred formal parameters and var-declared formal parameters in implicitly typed Java lambda expressions nor can you mix var-declared formal parameters and manifest types in explicitly typed lambda expressions. The following examples are not permitted:

				
					(var x, y) -> x.process(y)      // Cannot mix var and inferred formal parameters
                                // in implicitly typed lambda expressions
(var x, int y) -> x.process(y)  // Cannot mix var and manifest types
// in explicitly typed lambda expressions
				
			

For local variable declarations with initializers, enhanced for-loop indexes, and index variables declared in traditional for loops, allow the reserved type name var to be accepted in place of manifest types:

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

The identifier var is not a keyword; instead it is a reserved type name. This means that code that uses var as a variable, method, or package name will not be affected; code that uses var as a class or interface name will be affected (but these names are rare in practice, since they violate usual naming conventions).

Forms of local variable declarations that lack initializers, declare multiple variables, have extra array dimension brackets, or reference the variable being initialized are not allowed. Rejecting locals without initializers narrows the scope of the feature, avoiding “action at a distance” inference errors, and only excludes a small portion of locals in typical programs.

The inference process, substantially, just gives the variable the type of its initializer expression. Some subtleties:

    • The initializer has no target type (because we haven’t inferred it yet). Poly expressions that require such a type, like lambdas, method references, and array initializers, will trigger an error.
    • If the initializer has the null type, an error occurs—like a variable without an initializer, this variable is probably intended to be initialized later, and we don’t know what type will be wanted.
    • Capture variables, and types with nested capture variables, are projected to supertypes that do not mention capture variables. This mapping replaces capture variables with their upper bounds and replaces type arguments mentioning capture variables with bounded wildcards (and then recurs). This preserves the traditionally limited scope of capture variables, which are only considered within a single statement.

Other than the above exceptions, non-denotable types, including anonymous class types and intersection types, may be inferred. Compilers and tools need to account for this possibility.

Leave a Comment

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

Scroll to Top