web
×

JAVA 10 Tutorial



 

Java 10: Local Variable Type Inference


n 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 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.

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 types. 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:

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

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.

Copy
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.

Copy
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.

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”.
    Copy
    var var = 5; // syntactically correct // var is the name of the variable
  • “var” as a method name is allowed.
    Copy
    public static void var() { // syntactically correct }
  • “var” as a package name is allowed.
    Copy
    package var; // syntactically correct
  • “var” cannot be used as the name of a class or interface.
    Copy
    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

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:

Copy
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)); }

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 initailizer then the compiler will not be able to infer the type.

    Copy
    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
    Copy
    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.

    Copy
    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
    Copy
    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

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:

Copy
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, here the compiler cannot infer the LHS and hence it infers map2 to have upper bound or super type to which the HashMap can be denoted to. This leads to map2 being inferred as HashMap. 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, compiler cannot infer the RHS expression to any specific type. This leads to a formation of non-denotable Type.

Firstly, compiler will get denotable type by using the super type 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 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 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());

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)
  • Benefits of Local Variable Type Inference

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





journaldev is optimized for learning.© journaldev .
All Right Reserved and you agree to have read and accepted our term and condition