Close

Java 10 - Local-Variable Type Inference Quick Examples

[Updated: Jun 7, 2018, Created: Mar 23, 2018]

Java 10 introduces a new type inference feature for local variables. For the local variables, we can now use a special reserved type name var instead of actual type.

This feature reduces the boilerplate coding, while maintaining Java's compile time type checking.

Since compiler needs to infer the var actual type by looking at the right hand side (RHS), this feature has limitation in some cases.

Let's go through some quick examples to understand what can be or what cannot be done with this feature.


Examples

I already have downloaded and installed JDK 10 General-Availability Release:

D:\local-variable-type-inference-quick-examples>java -version
java version "10" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+46)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)

Simple type inference

In following example, the compiler can see the RHS is a String literal:

   public static void main(String[] args) {
       var str = "a test string";
       var substring = str.substring(2);
       System.out.println(substring);
       System.out.println(substring.getClass().getTypeName());
   }
test string
java.lang.String

Compile time safety is still there

Incompatible vars cannot be assigned to each other. Once compiler has inferred actual type of var, we cannot do wrong assignment:

   public static void main(String[] args) {
       var i = 10;
       i = "a string";
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\IncompatibleAssignment.java
src\com\logicbig\example\IncompatibleAssignment.java:6: error: incompatible types: String cannot be converted to int
i = "a string";
^
1 error

It's just like compiler has replaced 'var i = 10' with 'int i = 10' for further checking.


Polymorphism still works

A subtype var can be assigned to supertype var:

   public static void main(String[] args) {
       var formattedTextField = new JFormattedTextField("some text");
       var textField = new JTextField("other text");
       textField = formattedTextField;
       System.out.println(textField.getText());
   }
some text

A supertype var cannot be assigned to subtype var:

   public static void main(String[] args) {
       var formattedTextField= new JFormattedTextField();
       var textField = new JTextField();
        formattedTextField = textField;
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\Polymorphism2.java
src\com\logicbig\example\Polymorphism2.java:9: error: incompatible types: JTextField cannot be converted to JFormattedTextField
formattedTextField = textField;
^
1 error

Collection element type inference and Generics

In the following case, the compiler can see what is the type of collection elements.

   public static void main(String[] args) {
       var list2 = Arrays.asList(10);
       System.out.println(list2);
       //following needs no casting, which shows compiler has inferred correct element type int
       int i = list2.get(0);//equivalent to: var i = list2.get(0);
       System.out.println(i);
   }
[10]
10

In following case, compiler will just take it as collection of objects (not integers) that's because in case of the diamond operator, Java already needs a type on the LHS to infer type on RHS.

   public static void main(String[] args) {
       var list = new ArrayList<>();
       list.add(10);
       System.out.println(list);
       //need to cast to get int back
       int i= (int) list.get(0);
       System.out.println(i);
   }
[10]
10

In case of generics, we better need to use a specific type (instead of diamond operator) on the RHS:

   public static void main(String[] args) {
       var list = new ArrayList<Integer>();
       list.add(10);
       System.out.println(list);
       //no need to cast
       int i=  list.get(0);
       System.out.println(i);
   }
[10]
10

For Loop

   public static void main(String[] args) {
       for (var x = 1; x <= 5; x++) {
           var z = x * 3;//equivalent to: int z = x * 3;
           System.out.println(z);
       }
   }
3
6
9
12
15

For Each Loop

   public static void main(String[] args) {
       var list = Arrays.asList(1, 2, 3);
       for (var integer : list) {
           var z = integer * 3;//equivalent to: int z= integer * 3;
           System.out.println(z);
       }
   }
3
6
9

Java 8 Stream

   public static void main(String[] args) {
       var list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
       var stream = list.stream();
       stream.filter(i -> i % 2 == 0)
             .forEach(System.out::println);
   }
2
4
6

Ternary operator

   public static void main(String[] args) {
       var x = args.length > 0 ? args.length : -1;
       System.out.println(x);
       //the x is of type int
       int i = x; //no casting
   }
-1

What if we use different types of operands on RHS of the ternary operator? Let's see that:

   public static void main(String[] args) {
       var x = args.length > 0 ? args.length : "no args";
       System.out.println(x);
       System.out.println(x.getClass());
   }
no args
class java.lang.String
   public static void main(String[] args) {
       var x = args.length < 5 ? args.length : "More than 5 args not allowed";
       System.out.println(x);
       System.out.println(x.getClass());
   }
0
class java.lang.Integer

Do above two examples show that the type of the var is decided during runtime? Absolutely not! Let's do the same thing in the old way:

   public static void main(String[] args) {
       Serializable x = args.length > 0 ? args.length : "no args";
       System.out.println(x);
       System.out.println(x.getClass());
   }
no args
class java.lang.String

My IDE (Intellij) suggested to assign the LHS with Serializable. It's a common compatible and the most specialized type for the two different operands (the least specialized type would be java.lang.Object). Both String and Integer implement Serializable. Integer autoboxed from int. In other words Serializable is the LUB (Least Upper Bound) of the two operands (check out this). So this suggests that in our third last example, var type is also Serializable.


Passing to methods

public class PassVarToMethodExample {
  public static void main(String[] args) {
      var number = new BigDecimal("1.6");
      number = getSquareOf(number);
      System.out.println(number);
  }

  private static BigDecimal getSquareOf(BigDecimal number) {
      var result= number.multiply(number);
      return result;
  }
}
2.56
public class PassVarToMethodExample2 {
  public static void main(String[] args) {
      var numbers = List.of(1.1, 2.2, 3.3);
      System.out.println(numbers);
      var integers = toIntList(numbers);
      System.out.println(integers);
  }

  private static <T extends Number> List<Integer> toIntList(List<T> listOfNumber) {
      var integers = listOfNumber.stream()
                                 .map(Number::intValue)
                                 .collect(Collectors.toList());
      return integers;
  }
}
[1.1, 2.2, 3.3]
[1, 2, 3]

Anonymous Classes

   public static void main(String[] args) {
       var message = "running...";//effectively final
       var runner = new Runnable(){
           @Override
           public void run() {
               System.out.println(message);
           }
       };
       runner.run();
   }
running...

Limitations

Variable without initializer not allowed

   public static void main(String[] args) {
       var x;
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\UninitializedVar.java
src\com\logicbig\example\UninitializedVar.java:5: error: cannot infer type for local variable x
var x;
^
(cannot use 'var' on variable without initializer)
1 error

No Definite Assignment

Even assignments like the following (known as Definite Assignment) does not work for var:

   public static void main(String[] args) {
       boolean b = true;
       var x;
       if (b) {
           x = 1;
       } else {
           x = 2;
       }
       System.out.println(x);
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\DefiniteAssignment.java
src\com\logicbig\example\DefiniteAssignment.java:6: error: cannot infer type for local variable x
var x;
^
(cannot use 'var' on variable without initializer)
1 error

No Null Assignment

   public static void main(String[] args) {
       var str = null;
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\NullVar.java
src\com\logicbig\example\NullVar.java:5: error: cannot infer type for local variable str
var str = null;
^
(variable initializer is 'null')
1 error

No Lambda initializer

This is just like diamond operator case, RHS already needs the type inference from LHS.

   public static void main(String[] args) {
       var runnable = () -> {};
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\LambdaExample.java
src\com\logicbig\example\LambdaExample.java:5: error: cannot infer type for local variable runnable
var runnable = () -> {};
^
(lambda expression needs an explicit target-type)
1 error

No Method Reference initializer

Similar to lambda and diamond operator case:

   public static void main(String[] args) {
       var abs = BigDecimal::abs;
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\MethodReference.java
src\com\logicbig\example\MethodReference.java:7: error: cannot infer type for local variable abs
var abs = BigDecimal::abs;
^
(method reference needs an explicit target-type)
1 error

Not all array initializers work

var with [] do not work:

   public static void main(String[] args) {
       var numbers[] = new int[]{1, 2, 4};
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\ArrayExample.java
src\com\logicbig\example\ArrayExample.java:5: error: 'var' is not allowed as an element type of an array
var numbers[] = new int[]{1, 2, 4};
^
1 error

Following does not work (for the same reason, we cannot pass {1,2,4} to method arg or return from method etc):

   public static void main(String[] args) {
       var numbers = {1, 2, 4};
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\ArrayExample2.java
src\com\logicbig\example\ArrayExample2.java:5: error: cannot infer type for local variable numbers
var numbers = {1, 2, 4};
^
(array initializer needs an explicit target-type)
1 error

Just like the second last example, var and [] cannot be together on RHS:

   public static void main(String[] args) {
       var numbers[] = {1, 2, 4};
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\ArrayExample3.java
src\com\logicbig\example\ArrayExample3.java:5: error: 'var' is not allowed as an element type of an array
var numbers[] = {1, 2, 4};
^
1 error
Only following works:
   public static void main(String[] args) {
       var numbers = new int[]{1, 2, 4};
       var number = numbers[1];
       number = number + 3;
       System.out.println(number);
   }
5

No compound declaration

   public static void main(String[] args) {
       var x = 1, y = 3, z = 4;
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\MultiVars.java
src\com\logicbig\example\MultiVars.java:5: error: 'var' is not allowed in a compound declaration
var x = 1, y = 3, z = 4;
^
1 error

No var Fields allowed

public class FieldExample {
  private var i;
}
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\FieldExample.java
src\com\logicbig\example\FieldExample.java:4: error: 'var' is not allowed here
private var i;
^
1 error

No var Method parameters allowed

public class MethodExample {

  public void doSomething(var x){

  }
}
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\MethodExample.java
src\com\logicbig\example\MethodExample.java:5: error: 'var' is not allowed here
public void doSomething(var x){
^
1 error

No var as Method return type

public class MethodReturnTypeExample {

  public var getSomething(){
      return 5;
  }
}
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\MethodReturnTypeExample.java
src\com\logicbig\example\MethodReturnTypeExample.java:5: error: 'var' is not allowed here
public var getSomething(){
^
1 error

No var in catch clause

public class CatchClauseExample {
  public static void main(String[] args) {
      try {
          Files.readAllBytes(Paths.get("c:\temp\temp.txt"));
      } catch (var e) {
      }
  }
}
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\CatchClauseExample.java
src\com\logicbig\example\CatchClauseExample.java:10: error: 'var' is not allowed here
} catch (var e) {
^
1 error


What var is compiled to?

var is just a syntactic sugar and it does not have any new bytecode construct in the compiled code and during runtime JVM has no special instructions for them.


IDE support:

Intellij supports JDK 10 since version: Intellij IDEA 2018.1.

Eclipse supports JDK 10 since Version: Oxygen.3a Release.

Example Project

Dependencies and Technologies Used:

  • JDK 10
Java 10 Local-Variable Type Inference Quick Examples Select All Download
  • local-variable-type-inference-quick-examples
    • src
      • com
        • logicbig
          • example
            • ListExample.java

    See Also