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 ProjectDependencies and Technologies Used: |