Key Takeaways
- Java 10 introduces a shiny new feature: type inference for local variables. For local variables, you can now use a special reserved type name “var” instead of actual type.
- This feature is provided to enhance the Java language and extend type inference to declarations of local variables with initializers. This reduces the boilerplate code required, while still maintaining Java’s compile time type checking.
- Since the compiler needs to infer the var actual type by looking at the Right-Hand Side (RHS), this feature has limitations in some cases, such as when initialising Arrays and Streams.
- Experiment with this hands-on tutorial on how to reduce the boilerplate code using new “var” type.
In this article I am going to introduce, by example, the new Java SE 10 feature “var” type. You will learn how to use it properly in your code, and also when you can’t use it.
Introduction
Java 10 introduces a shiny new feature: type inference for local variables. For local variables, you can now use a special reserved type name “var” instead of actual type, as demonstrated by the following:
var name = “Mohamed Taman”;
This feature is provided to enhance the Java language and extend type inference to declarations of local variables with initializers. This reduces the boilerplate code required, while still maintaining Java’s compile time type checking.
Since the compiler needs to infer the var actual type by looking at the Right-Hand Side (RHS), this feature has limitations in some cases. I am going to mention this after a while, just keep reading. Let’s go through some quick examples now.
Hey, wait, wait and wait! Before jumping into the code, you will need to use an IDE to try out the new features as usual. The good news is that there are many in the market, so you can choose your favorite IDE that supports Java SE 10 among many IDEs like Apache NetBeans 9, IntelliJ IDEA 2018, or new Eclipse.
Personally, I always prefer to use an interactive programming environment tool, to quickly learn Java language syntax, explore new Java APIs and its features, and even for prototyping complex code. This is instead of the tedious cycle of editing, compiling and executing code which typically involves the following process:
- Write a complete program.
- Compile it and fix any errors.
- Run the program.
- Figure out what is wrong with it.
- Edit it.
- Repeat the process.
The great news again is that you are going to use the JShell tool that is built-in and shipped with Java SE JDK since Java SE 9, which was a flagship feature of that release.
What is JShell
Now Java has a rich REPL (Read-Evaluate-Print-Loop) implementation with JShell tool, referred as an Java Shell as an interactive programming environment. So, what’s the magic? It’s simple. JShell provides a fast and friendly environment that enables you to quickly explore, discover and experiment with Java language features and its extensive libraries.
Using JShell, you can enter program elements one at a time, immediately see the result, and make adjustments as needed. Therefore JShell replaces the tedious cycle of editing, compiling and executing with its read-evaluate-print loop. Rather than complete programs, In JShell you write JShell commands and Java code snippets.
When you enter a snippet, JShell immediately reads, evaluates, and prints its results. Then it loops to perform this process again for the next snippet. So, JShell and its instant feedback keeps your attention, enhances your performance and speeds up the learning and software development processes.
That is enough introduction for JShell, and InfoQ have recently published a thorough introduction to the tool. To deep dive and learn more about all JShell features, I have recorded an entire video training about this topic titles “Hands-on Java 10 Programming with JShell [Video]” that should help you to master the topic, and can be reached either from Packt or Udemy.
So now, let’s go through some quick examples to understand what can be done with this new var type feature using JShell.
Required Software
To work probably with JShell, I am assuming that you have Java SE or JDK 10+ installed and tools in JDK bin folder is configured to be accessible from anywhere in your system, if not here is the link to install the JDK 10+ latest release.
Starting a JShell Session
To start a JShell session in:
- Microsoft Windows open a Command Prompt then type jshell and press Enter.
- On Linux, open a shell window then type jshell and press Enter.
- While on macOS (formerly OS X), open a Terminal window, then type the following command “jshell” and press Enter.
Taraaa! This command executes a new JShell session, and displays this message at the jshell> prompt:
| Welcome to JShell -- Version 10.0.1
| For an introduction type: /help intro
jshell>
Working with “var” type.
Now you have JDK 10 installed, let’s start playing with JShell, so let’s jump right ahead to the terminal to start hacking var type feature capabilities with examples. Just enter each of upcoming snippets I am introducing next at the jshell prompt, and I will leave the result for you to explore as an exercise. If you had a sneaky look ahead at the code, you will notice that it looks wrong, as there is no semicolons. Try it and see if it works or not.
Simple type inference case
This is the basic usage of var type, in the following example, the compiler can infer the RHS as a String literal:
var name = "Mohamed Taman"
var lastName = str.substring(8)
System.out.println("Value: "+lastName +" ,and type is: "+ lastName.getClass().getTypeName())
No semicolon is required because JShell is an interactive environment. A semicolon is only required when there are multiple statements on the same line, or statements inside a declared type or method, and you will see this the following examples.
var type and inheritance
Also, polymorphism still works. In the world of inheritance, a subtype of var type can be assigned to super type of var type as normal cases as the following:
import javax.swing.*
var password = new JPasswordField("Password text")
String.valueOf(password.getPassword()) // To convert password char array to string to see the value
var textField = new JTextField("Hello text")
textField = password
textField.getText()
But a super type var cannot be assigned to subtype var as the following:
password = textField
This is because JPasswordField is a subclass of JTextField class.
var and compile time safety
So now, what about wrong assignment?It is an easy answer; incompatible variable types cannot be assigned to each other. Once compiler has inferred actual type of var, you cannot assign wrong value as the following:
var number = 10
number = "InfoQ"
So, what happens here? the compiler here has just replaced “var number = 10” with “int number = 10” for further checking, safety is still maintained.
var with Collections & Generics
Okay let’s see how var works with Collection element type inference and Generics. Let’s start first with collections. In the following case, the compiler can infer what the type of collection elements is:
var list = List.of(10);
There is no need to cast here, as compiler has inferred correct element type int
int i = list.get(0); //equivalent to: var i = list.get(0);
In following case the situation is different, the compiler will just take it as collection of objects (not integers) that's because when you use the diamond operator, Java already needs a type on the LHS (Left Hand Side) to infer type on the RHS, let’s see how;
var list2 = new ArrayList<>(); list2.add(10); list2
int i = list2.get(0) //Compilation error
int i = (int) list2.get(0) //need to cast to get int back
In the case of generics, you better need to use a specific type (instead of diamond operator) on the RHS as the following:
var list3 = new ArrayList<Integer>(); list3.add(10); System.out.println(list3)
int i = list3.get(0)
Let’s jump right to see how var type works inside the different kind of loops:
var type inside for loops
Let’s check first the index based normal For Loop
for (var x = 1; x <= 5; x++) {
var m = x * 2; //equivalent to: int m = x * 2;
System.out.println(m);
}
And here is how it used with For Each Loop
var list = Arrays.asList(1,2,3,4,5,6,7,8,9,10)
for (var item : list) {
var m = item + 2;
System.out.println(m);
}
So now I have a question here, does var works with a Java 8 Stream? Let’s see with the following example;
var list = List.of(1, 2, 3, 4, 5, 6, 7)
var stream = list.stream()
stream.filter(x -> x % 2 == 0).forEach(System.out::println)
var type with Ternary operator
What about the Ternary operator?
var x = 1 > 0 ? 10 : -10
int i = x
Now, what if you use different types of operands on RHS of the ternary operator? Let's see:
var x = 1 > 0 ? 10 : "Less than zero"; System.out.println(x.getClass()) //Integer
var x = 1 < 0 ? 10 : "Less than zero"; System.out.println(x.getClass()) // String
Do those 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:
Serializable x = 1 < 0 ? 10 : "Less than zero"; System.out.println(x.getClass())
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 auto-boxed from int. In other words, Serializable is the LUB(Least Upper Bound) of the two operands. So, this suggests that in our third last example, var type is also Serializable.
Let’s move onto another topic: passing var type to methods.
var type with methods
Let’s first declare a method called squareOf with one argument of type BigDecimal, which return the square of this argument as the following:
BigDecimal squareOf(BigDecimal number) {
var result= number.multiply(number);
return result;
}
var number = new BigDecimal("2.5")
number = squareOf(number)
Now let’s see how it works with generics; again let’s declare a method called toIntgerList with one argument of type List of type T(a generic type), which returns an integer-based list of this argument using the Streams API as the following:
<T extends Number> List<Integer> toIntgerList(List<T> numbers) {
var integers = numbers.stream()
.map(Number::intValue)
.collect(Collectors.toList());
return integers;
}
var numbers = List.of(1.1, 2.2, 3.3, 4.4, 5.5)
var integers = toIntgerList(numbers)
var with Anonymous Classes
Finally, let’s look at using var with anonymous classes. Let’s take advantage of threading by implementing Runnable interface as the following:
var message = "running..." //effectively final
var runner = new Runnable(){
@Override
public void run() {
System.out.println(message);
}}
runner.run()
So far I have introduced the shiny new Java 10 feature “var” type, which reduces the boilerplate coding while maintaining Java’s compile time type checking. And you went through examples shows what can be done with it. Now you will learn about the limitations of the var type, and where it is not allowed.
“var” limitations
Now you are going to take a look at some quick examples to understand what cannot be done with var type feature. So, let’s jump right ahead to the terminal to hack limitations with some examples.
The results of jshell prompt will explain what is wrong with code, so you can take advantage of interactive instant feedback.
You should initialize with a value
The first, and easiest, thing is that a variable without an initializer is not allowed here;
var name;
You will get a compilation error; because the compiler cannot infer type for this local variable x.
Compound declaration is not allowed
Try to run this line;
var x = 1, y = 3, z = 4
And you will get this error message, 'var' is not allowed in a compound declaration.
No Definite Assignment
Try to create a method called testVar as the following, just copy and paste the method into the JShell:
void testVar(boolean b) {
var x;
if (b) {
x = 1;
} else {
x = 2;
}
System.out.println(x);
}
It will not create the method, and will instead throw a compilation error. You cannot use 'var' on variable without an initializer. Even assignments like the following (known as Definite Assignment) do not work for var.
Null Assignment
Null assignment is not allowed, as shown in the following;
var name = null;
This will throw an Exception “variable initializer is 'null'” . Because null is not a type.
Working with Lambdas
Another example, with no Lambda initializer. This is just like diamond operator case, RHS already needs the type inference from LHS.
var runnable = () -> {}
This Exception will be thrown, “lambda expression needs an explicit target-type”.
var and Method Reference
No method reference initializer, similar case to lambda and diamond operator:
var abs = BigDecimal::abs
This Exception will be thrown: “method reference needs an explicit target-type”
var and Array Initializations
Not all array initializers work, let’s see how var with [] does not work:
var numbers[] = new int[]{2, 4, 6}
The error will be: 'var' is not allowed as an element type of an array.
The following also does not work:
var numbers = {2, 4, 6}
The error is: “array initializer needs an explicit target-type”
Just like the last example, var and [] cannot be together on LHS:
var numbers[] = {2, 4, 6}
error: 'var' is not allowed as an element type of an array
Only the following array initialization works:
var numbers = new int[]{2, 4, 6}
var number = numbers[1]
number = number + 3
No var Fields allowed
class Clazz {
private var name;
}
No var Method parameters allowed
void doAwesomeStuffHere(var salary){}
No var as Method return type
var getAwesomeStuff(){ return salary; }
No var in catch clause
try {
Files.readAllBytes(Paths.get("c:\temp\temp.txt"));
} catch (var e) {}
What happens behind the scene for var type at compile time?
“var” really is just a syntactic sugar, and it does not introduce any new bytecode construct in the compiled code, and during runtime the JVM has no special instructions for them.
Conclusion.
Wrapping up the article, you have covered what the “var” type is and how this feature reduces the boilerplate coding, while maintaining Java’s compile time type checking.
You then learned about the new JShell tool, Java’s REPL implementation, that help you to learn quickly Java language, and explore new Java APIs and its features. You can also prototype complex code using JShell, instead of the traditional tedious cycle of editing, compiling and executing code.
Finally, you learned about all of the var type capabilities and limitations, such as where you can and can’t use it. It was fun writing this article for you, and so I hope you liked it and found it useful. If so, then please spread the word.
Resources
- JDK 10 Documentation
- Hands-on Java 10 Programming with JShell.
- Getting Started with Clean Code Java SE 9.
- Overview of JDK 10 and JRE 10 Installation.
- JEP 286: Local-Variable Type Inference.
- Definite Assignment
About the Author
Mohamed Taman is Sr. Enterprise Architect / Sr. Software Engineer @WebCentric, Belgrade, Serbia | Java Champions | Oracle Developer Champions | JCP member | Author | EGJUG Leader | International Speaker. Tweeter Taman @_tamanm