Close

Java 10 - Local-Variable Type Inference Quick Examples

[Updated: Dec 8, 2018, Created: Mar 23, 2018]

Java 10 introduced a new type inference feature for local variables. For the local variables, we can use a special reserved type name var instead of actual type (var is not a keyword).

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

D:\local-variable-type-inference-quick-examples>java -version
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, 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

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

Consider following example:

   public static void main(String[] args) {
       //list not equivalent to List<Object> but List<Object&Serializable&Comparable>
       var list = new ArrayList<>(Arrays.asList(10, "two"));
       System.out.println(list);
       Serializable  s = list.get(0);
       System.out.println(s);
       Comparable c = list.get(0);
       System.out.println(c);
       Object o = list.get(0);//still ok
       System.out.println(o);
       //Integer i = list.get(0); not ok
       //String s = list.get(1); not ok
       list.add(BigDecimal.valueOf(3.3));//ok BigDecimal implements both Serializable & Comparable
       list.add(new Date());//ok Date implements both Serializable & Comparable
       System.out.println(list);
   }
[10, two]
10
10
10
[10, two, 3.3, Fri Dec 07 14:27:42 CST 2018]

In above example, the compiler's inferred type for list elements is the most specialized type common to both Integer and String. Since Comparable and Serializable both are implemented by these two types their intersection will be the common type i.e. Object & Comparable & Serializable. Any type which implements both these interfaces can be added to the list.

In above example, if we try to add anything which does not implement both Serializable and Comparable:

   public static void main(String[] args) {
       var list = new ArrayList<>(Arrays.asList(10, "two"));
       //Currency is Serializable but not Comparable
       list.add(Currency.getInstance("USD"));
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\ListExample3.java
src\com\logicbig\example\ListExample3.java:11: error: incompatible types: Currency cannot be converted to INT#1
list.add(Currency.getInstance("USD"));
^
where INT#1,INT#2 are intersection types:
INT#1 extends Object,Serializable,Comparable<? extends INT#2>
INT#2 extends Object,Serializable,Comparable<?>
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

Collection element type inference and Generics

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). How about Comparable, isn't that also implemented by both String and Integer?. Let's do a test:

   public static void main(String[] args) {
       var x = args.length > 0 ? args.length : "no args";
       x = new Date();//ok because Date implements both Serializable and Comparable
       x = Currency.getInstance("USD"); // implements Serializable but not Comparable
   }
D:\local-variable-type-inference-quick-examples>javac -d out src\com\logicbig\example\TernaryOperator5.java
src\com\logicbig\example\TernaryOperator5.java:10: error: incompatible types: Currency cannot be converted to INT#1
x = Currency.getInstance("USD"); // implements Serializable but not Comparable
^
where INT#1,INT#2 are intersection types:
INT#1 extends Object,Serializable,Comparable<? extends INT#2>
INT#2 extends Object,Serializable,Comparable<?>
1 error

LUB is nothing but the intersection of the types. In above example compiler sees var type as intersection of Object, Serializable and Comparable (Object & Serializable & Comparable)


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;
^
src\com\logicbig\example\MultiVars.java:5: error: 'var' is not allowed in a compound declaration
var x = 1, y = 3, z = 4;
^
2 errors

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