26 Items for Dissecting Java Local Variable Type Inference (Var Type)
Learn everything you need to know and more about the var type inference!
Join the DZone community and get the full member experience.
Join For FreeThis article is also part of my book Java Coding Problems.
Let's get started!
The Java Local Variable Type Inference (LVTI), or shortly, the var
type (the identifiervar
is not a keyword, is a reserved type name), was added in Java 10 via JEP 286: Local-Variable Type Inference. As a 100 percent compile feature, it doesn't affect bytecode, runtime, or performance. Mainly, the compiler will inspect the right-hand side and infer the concrete type. It looks at the right-hand side of the declaration, and if there is an initializer, then it simply uses that type to replacevar
. Addtionally, it is useful to reduce verbosity, redundancy, and boilerplate code. It is also meant to speed up the ceremonies involved while writing code. For example, it is very handy to writevar evenAndOdd =...
instead of Map <Boolean, List <Integer>> evenAndOdd...
. Depending on the use case, it has a trade-off in code readability that is covered in the first item below.
Further, it is a list of 26 items meant to cover thevar
type use cases including its limitations.
Item 1: Strive for Meaningful Local Variables Names
Commonly, we focus on meaningful names for global variables, but we don't put the same attention in choosing the local variables names. Especially when our methods are short, have good names, and implementations, we may tend to drastically shortcut the local variables names. But when we go withvar
instead of explicit types, the concrete types are inferred by the compiler. As a consequence, it is more difficult for humans to read/understand the code. This is the place wherevar
can cut a piece of readability of the code. Most of the time, this is happening because we tend to look at the type as the primary information and to the variable name as the secondary information, while this should be vice versa.
Example 1:
Even here, some people may still sustain that local variables names are too short. Let's take a look:
// HAVING
public boolean callDocumentationTask() {
DocumentationTool dtl = ToolProvider.getSystemDocumentationTool();
DocumentationTask dtt = dtl.getTask(...);
return dtt.call();
}
When using or switching tovar
, avoid:
// AVOID
public boolean callDocumentationTask() {
var dtl = ToolProvider.getSystemDocumentationTool();
var dtt = dtl.getTask(...);
return dtt.call();
}
Prefer:
// PREFER
public boolean callDocumentationTask() {
var documentationTool = ToolProvider.getSystemDocumentationTool();
var documentationTask = documentationTool.getTask(...);
return documentationTask.call();
}
Example 2:
Avoid:
// AVOID
public List<Product> fetchProducts(long userId) {
var u = userRepository.findById(userId);
var p = u.getCart();
return p;
}
Prefer:
// PREFER
public List<Product> fetchProducts(long userId) {
var user = userRepository.findById(userId);
var productList = user.getCart();
return productList;
}
Example 3:
Striving to use meaningful names for local variables doesn't mean to fall into the over-naming technique.
Avoid having a single type of output stream in a short method:
// AVOID
var byteArrayOutputStream = new ByteArrayOutputStream();
Instead, use this, which should be clear enough:
// PREFER
var outputStream = new ByteArrayOutputStream();
// or
var outputStreamOfFoo = new ByteArrayOutputStream();
Lastly, did you know that Java internally uses a class named:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
Well, naming a variable of this type should be challenging :)
Item 2: Use literals to Help var to Infer the Expected Primitive Type (int, long, float, double)
Without using literals for the primitive types, we may discover that the expected and inferred types may differ. This is caused by the implicit type casting used by thevar
type.
For example, the following two snippets of code are behaving as expected. First, we declare a boolean
and achar
using explicit types:
boolean flag = true; // this is of type boolean
char a = 'a'; // this is of type char
Now, we usevar
instead of explicit primitive types:
var flag = true; // this is inferred as boolean
var a = 'a'; // this is inferred as char
Okay, so far, so good. Next, let's follow the same logic for anint
, along
, adouble
, and afloat
:
int intNumber = 20; // this is of type int
long longNumber = 20; // this is of type long
float floatNumber = 20; // this is of type float, 20.0
double doubleNumber = 20; // this is of type double, 20.0
While the above snippet of code is trivial and clear, now let's usevar
instead of explicit types.
Avoid:
// AVOID
var intNumber = 20; // this is inferred as int
var longNumber = 20; // this is inferred as int
var floatNumber = 20; // this is inferred as int
var doubleNumber = 20; // this is inferred as int
So, all four variables have been inferred asint
. In order to fix this behavior, we need to rely on Java literals.
Prefer:
// PREFER
var intNumber = 20; // this is inferred as int
var longNumber = 20L; // this is inferred as long
var floatNumber = 20F; // this is inferred as float, 20.0
var doubleNumber = 20D; // this is inferred as double, 20.0
But what is happening if we declare a number using some explicit decimals?
Avoid doing this if you think that your number is a float
:
// AVOID, IF THIS IS A FLOAT
var floatNumber = 20.5; // this is inferred as double
Prefer using the corresponding literal in order to avoid this issue:
// PREFER, IF THIS IS A FLOAT
var floatNumber = 20.5F; // this is inferred as float
Item 3: In Certain Cases, Var and Implicit Type Casting May Sustain Maintainability
In certain cases, relying onvar
and implicit type casting may sustain maintainability. For example, let's assume that our code sits between two methods of an API (or services, endpoints, etc). One method receives a shopping cart with different items and computes the best price by comparing different prices on the market and return the total price as a float
. Another method simply debits thisfloat
price on a card.
First, let's look at the API method that computes the best price:
public float computeBestPrice(String[] items) {
...
float price = ...;
return price;
}
Second, let's check out the API method that reflects the debit card:
public boolean debitCard(float amount, ...) {
...
}
Now, we put our code between these two external services methods as a client of them. Our users/customers can choose the items to buy, and we compute the best price for them and debit their cards:
// AVOID
public void purchaseCart(long customerId) {
...
float price = computeBestPrice(...);
debitCard(price, ...);
}
After a while, the company that owns the API decides to cut off the prices' decimals as a discount policy and useint
instead offloat
. Of course, we update the dependencies as well. So, they modify the API code like this:
public int computeBestPrice(String[] items) {
...
float realprice = ...;
...
int price = (int) realprice;
return price;
}
public boolean debitCard(int amount, ...) {
...
}
The problem is that our code is using explicit float;
therefore, it will not tolerate these modifications well. The code will produce compile-time errors. But if we have anticipated such situations and usedvar
instead of float
, then our code will work without problems thanks to the implicit type casting:
// PREFER
public void purchaseCart(long customerId) {
...
var price = computeBestPrice(...);
debitCard(price, ...);
}
Item 4: When literals Are Not a Solution, Rely on Explicit Downcast or Better Avoid var
Some Java primitives types don't take advantage of literals. For example, this includes byte
andshort
primitive types. By using the explicit primitive types, we can do this with no issue.
Use this instead of usingvar
:
// PREFER THIS INSTEAD OF USING VAR
byte byteNumber = 45; // this is of type byte
short shortNumber = 4533; // this is of type short
But, why to prefer in this situation explicit types instead of usingvar
? Well, let's switch the above code tovar
. Notice that the inferred type isint
in both cases, so not the expected types.
And, avoid the following:
// AVOID
var byteNumber = 45; // this is inferred as int
var shortNumber = 4533; // this is inferred as int
There are no literals to jump in here and help us, so we need to rely on an explicit downcast. Personally, I will avoid doing this since I don't see any advantage in it, but it works.
Use this only if you really want to usevar
:
// PREFER THIS ONLY IF YOU WANT TO USE VAR
var byteNumber = (byte) 45; // this is inferred as byte
var shortNumber = (short) 4533; // this is inferred as short
Item 5: Avoid Using var if the Called names Don't Contain Enough Type Information for Humans
Usingvar
has the advantage of providing more concise code. For example, in the case of using constructors (which is a common use case for local variables), we can simply avoid the necessity of repeating the class name and, therefore, eliminate redundancy.
Avoid the following:
// AVOID
MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...);
Instead, use:
// PREFER
var inputStream = new MemoryCacheImageInputStream(...);
Or, in a construction like below, var
is, again, a nice approach for simplifying the code without losing information.
Avoid:
// AVOID
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(...);
Instead, use the following code:
// PREFER
var compiler = ToolProvider.getSystemJavaCompiler();
var fileManager = compiler.getStandardFileManager(...);
Well, why do we fill comfortable with thesevar-
based examples? Because the needed information is there in the called names. But whenvar
, in combination with the called names, leads to the loss of information, then it is better to avoid the usage ofvar
.
Avoid:
// AVOID
public File fetchCartContent() {
return new File(...);
}
// As a human, is hard to infer the "cart" type without
// inspecting the fetchCartContent() method
var cart = fetchCartContent();
Instead, use:
// PREFER
public File fetchCartContent() {
return new File(...);
}
File cart = fetchCartContent();
Or, think of an example based onjava.nio.channels.Selector
. This class has astatic
method namedopen()
that returns a new and openSelector
but is easy to think thatSelector.open()
may return aboolean
representing the success or unsuccess of opening the current selector, or evenvoid
. Usingvar
while losing information can induce exactly these kinds of confusions.
Item 6: the var Type Is Ensuring Compile-Time Safety
Thevar
type is compile-time safety. This means that we cannot compile an application, which tries to achieve a wrong assignment. For example, the below code will not compile:
// IT DOESN'T COMPILE
var items = 10;
items = "10 items"; // incompatible types: String cannot be converted to int
While this will successfully compile:
var items = 10;
items = 20;
And, this successfully compiles as well:
var items = "10";
items = "10 items" ;
So once the compiler has inferred the concrete/actual type ofvar,
we can assign the only values of that type.
Item 7: var Cannot Be Used to Create an Instance of a Concrete Type and Assign it to a Variable of an Interface Type
In Java, we are used with the "programming to the interface" technique.
For example, we create an instance ofArrayList,
as shown below (bind the code to the abstraction):
List<String> products = new ArrayList<>();
And, we avoid something like this (bind the code to the implementation):
ArrayList<String> products = new ArrayList<>();
So, it is preferable to follow the first example and instantiate theArrayList
class, but we also need to declare a variable of typeList
. SinceList
is an interface (a contract), we can easily switch the instantiation to other implementation ofList
without further modifications.
Well, while "programming to the interface" is the way to go, var
cannot take advantage of it. This means that when we usevar,
the inferred type is the concrete implementation. For example, in the next snippet of code, the inferred type is ArrayList <String>
:
var productList = new ArrayList<String>(); // inferred as ArrayList<String>
There are several arguments that sustain this behavior:
First,
var
is used for local variables, where, in most of the cases, "programming to the interface" is less "exploited" than in the case of method parameters/return types or fields typesThe scope of local variables should be small, so in case of issues caused by switching to another implementation, it should have a small impact in detecting and fixing them
var
perceives the code from its right as being an initializer used for inferring the actual type, and if, in the future, the initializer will be modified, then the inferred type may differ, resulting in issues in the subsequent code that relies on this variable;
Item 8: the Possibility of Unexpected Inferred Types
The var type combined with the diamond operator may lead to unexpected inferred types if the information needed for inferring the expected type is not present
Before Java 7 in Project Coin, we'd write something like this:
// explicitly specifying generic class's instantiation parameter type
List<String> products = new ArrayList<String>();
From Java 7 onwards using Project Coin, we have the diamond operator, which is capable of inferring the generic class instantiation parameter type, as follows:
// inferring generic class's instantiation parameter type
List<String> products = new ArrayList<>();
Having this example on the table andvar
on the learning list, you may ask what will be the inferred type of the below code?
First, you should avoid using:
// AVOID
var productList = new ArrayList<>(); // is inferred as ArrayList<Object>
The inferred type will be theArrayList
of theObject
. This is happening because the information needed for inferring the expected type (String
) is not present. This causes the inferred type to be the broadest applicable type, which, in this case, isObject
.
So excepting the case when this is what we want, we must provide the information needed for inferring the expected type. This can be done directly or can be derived.
Prefer (directly):
// PREFER
var productList = new ArrayList<String>(); // inferred as ArrayList<String>
Prefer (derived):
var productStack = new ArrayDeque<String>();
var productList = new ArrayList<>(productStack); // inferred as ArrayList<String>
Prefer (derived):
Product p1 = new Product();
Product p2 = new Product();
var listOfProduct = List.of(p1, p2); // inferred as List<Product>
// DON'T DO THIS
var listofProduct = new ArrayList<>(); // inferred as ArrayList<Object>
listofProduct.add(p1);
listofProduct.add(p2);
Item 9: Assigning an Array to var Doesn't Require Brackets, []
We all know how to declare an array in Java. We do it like this (check the left-side brackets):
int[] numbers = new int[5];
// or, less preferred
int numbers[] = new int[5];
Now, how about using var
? In this case, there is no need to use brackets in the left side.
Avoid the following (this will not even compile):
// IT DOESN'T COMPILE
var[] numbers = new int[5];
// or
var numbers[] = new int[5];
Instead, use:
// PREFER
var numbers = new int[5]; // inferred as array of int
numbers[0] = 2; // work
numbers[0] = 2.2; // doesn't work
numbers[0] = "2"; // doesn't work
Also, this usage ofvar
doesn't compile. This is happening because the right-hand side doesn't have its own type:
// explicit type work as expected
int[] numbers = {1, 2, 3};
// IT DOESN'T COMPILE
var numbers = {1, 2, 3};
var numbers[] = {1, 2, 3};
var[] numbers = {1, 2, 3};
Item 10: the var type cannot be used in compound declarations
If you are a fan of compound declarations, then you have to know thatvar
is not allowed in such declarations. The following code doesn't compile:
// IT DOESN'T COMPILE
// error: 'var' is not allowed in a compound declaration
var hello = "hello", bye = "bye", welcome = "welcome";
Instead, use:
// PREFER
String hello = "hello", bye = "bye", welcome = "welcome";
Or, use:
// PREFER
var hello = "hello";
var bye = "bye";
var welcome = "welcome";
Item 11: Local Variables Should Strive to Minimize Their Scope. The var Type Reinforces This Statement.
Keep a small scope for local variables — I am sure that you heard this statement beforevar
exists.
Readability and quick bug fixes are arguments that sustain this statement. For example, let's define a Java stack as follows:
Avoid:
// AVOID
...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// 50 lines of code that doesn't use stack
// George, Tyllen, Martin, Kelly
stack.forEach(...);
...
Notice that we call theforEach()
method, which is inherited from the java.util.Vector
. This method will traverse the stack as any vector, and this is what we want. Now, we decide to switch fromStack
toArrayDeque
. When we do that, theforEach()
method will be the one fromArrayDeque,
which will traverse the stack as a stack (LIFO).
// AVOID
...
var stack = new ArrayDeque<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// 50 lines of code that doesn't use stack
// Kelly, Martin, Tyllen, George
stack.forEach(...);
...
This is not what we want, but it is hard to see that a bug was introduced since the code containing theforEach()
part is not in the proximity of the code where the developer completed the modifications. In order to maximize the chances of getting this bug fixed quickly and avoid a bunch of scrolls up and down to understand what is happening, it is much better to write this code with a small scope for thestack
variable.
It is best to use the following:
// PREFER
...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// George, Tyllen, Martin, Kelly
stack.forEach(...);
...
// 50 lines of code that doesn't use stack
Now, when the developer switches fromStack
toArrayQueue,
they should notice the bug faster and fix it.
Item 12: the var Type Facilitates Different Types of Operands on the Right-Hand Side of the Ternary Operator
We can use different types of operands on the right-hand side of a ternary operator.
With explicit types, we cannot compile this:
// IT DOESN'T COMPILE
List code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// or
Set code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Nevertheless, we can do this:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Or, we cannot compile this:
// IT DOESN'T COMPILE
int code = intOrString ? 12112 : "12112";
String code = intOrString ? 12112 : "12112";
But, we can do this:
Serializable code = intOrString ? 12112 : "12112";
Object code = intOrString ? 12112 : "12112";
In such cases, we prefer var
:
// PREFER
// inferred as Collection<Integer>
var code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// inferred as Serializable
var code = intOrString ? 12112 : "12112";
Don't conclude from these examples thevar
type is inferred at runtime! It is NOT!
Of course,var
works when we use the same types of operands as well:
// inferred as float
var code = oneOrTwoDigits ? 1211.2f : 1211.25f;
Item 13: the var Type Can Be Used Inside for Loops
We can easily replace explicit types used insidefor
loops with thevar
type. Here are two examples.
Replace the explicit typeint
with var
:
// explicit type
for (int i = 0; i < 5; i++) {
...
}
// using var
for (var i = 0; i < 5; i++) { // i is inferred of type int
...
}
Then, replace the explicit typeOrder
withvar
:
List<Order> orderList = ...;
// explicit type
for (Order order : orderList) {
...
}
// using var
for (var order : orderList) { // order type is inferred as Order
...
}
Item 14: the var Type Works Just Fine With the Java 8 Stream
It is pretty straightforward to combine Java 10var
with the Java 8 Stream
.
You will need to replace the explicit typeStream
withvar
:
Example 1
// explicit type
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
// using var
var numbers = Stream.of(1, 2, 3, 4, 5); // inferred as Stream<Integer>
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
Example 2
// explicit types
Stream<String> paths = Files.lines(Path.of("..."));
List<File> files = paths.map(p -> new File(p)).collect(toList());
// using var
var paths = Files.lines(Path.of("...")); // inferred as Stream<String>
var files = paths.map(p -> new File(p)).collect(toList()); // inferred as List<File>
Item 15: the var Type Can Be Used to Declare Local Variables Meant to Break Up Nested/Large Chains of Expressions
The var type can be used to declare local variables, which is meant to break up nested/large chains of expressions
Large/nested expression look impressive, and usually, they are perceived as clever pieces of code. Sometimes, we wrote them like this on purpose; another time, we start from a small expression and keep growing its logic until we end up with a large one. In order to increase readability of the code, it is advisable to break a large/nested expression using local variables, but sometimes, adding these local variables seems to be an exhausting work that we want to avoid. This is how we obtain the expressions shown below.
Avoid:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// AVOID
int result = intList.stream()
.collect(Collectors.partitioningBy(i -> i % 2 == 0))
.values()
.stream()
.max(Comparator.comparing(List::size))
.orElse(Collections.emptyList())
.stream()
.mapToInt(Integer::intValue)
.sum();
Prefer:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
Map<Boolean, List<Integer>> evenAndOdd = intList.stream()
.collect(Collectors.partitioningBy(i -> i % 2 == 0));
Optional<List<Integer>> evenOrOdd = evenAndOdd.values()
.stream()
.max(Comparator.comparing(List::size));
int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
.stream()
.mapToInt(Integer::intValue)
.sum();
I think that the second snippet of code is more readable and clear, but there is nothing wrong with sustaining the first approach, as well. It is absolutely normal for our minds to adapt to understanding such large expressions and prefer them against local variables. Nevertheless, the triviality of using the var
type is a temptation for adopting local variables style because it saves the time spent to fetch the explicit types.
Prefer:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
var evenAndOdd = intList.stream()
.collect(Collectors.partitioningBy(i -> i % 2 == 0));
var evenOrOdd = evenAndOdd.values()
.stream()
.max(Comparator.comparing(List::size));
var sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
.stream()
.mapToInt(Integer::intValue)
.sum();
Item 16: the var Type Cannot Be Used As a Method Return Type or Method Arguments Type
Trying to write something in the following two snippets of code will not compile.
Usingvar
as a method return type:
// IT DOESN'T COMPILE
public var countItems(Order order, long timestamp) {
...
}
Usingvar
as a method arguments type:
// IT DOESN'T COMPILE
public int countItems(var order, var timestamp) {
...
}
Item 17: the Local Variables of the Type var Can Be Passed as a Method Arguments or Store Method Return
Thevar
type can be used for local variables that store or intercept the result of calling a method or will be passed to a method as arguments. The below snippet of code will compile and is working fine.
public int countItems(Order order, long timestamp) {
...
}
public boolean checkOrder() {
var order = ...; // an Order instance
var timestamp = ...; // a long representing a timestamp
var itemsNr = countItems(order, timestamp); // inferred as int type
...
}
It works with generics, as well. The below snippet of code is working fine.
public <A, B> B contains(A container, B tocontain) {
...
}
var order = ...; // Order instance
var product = ...; // Product instance
var resultProduct = contains(order, product); // inferred as Product type
Item 18: the var Type can be used with anonymous classes
Using thevar
type with anonymous classes is pretty straightforward and easy to intuit.
Avoid:
public interface Weighter {
int getWeight(Product product);
}
// AVOID
Weighter weighter = new Weighter() {
@Override
public int getWeight(Product product) {
...
}
};
Product product = ...; // a Product instance
int weight = weighter.getWeight(product);
Prefer:
public interface Weighter {
int getWeight(Product product);
}
// PREFER
var weighter = new Weighter() {
@Override
public int getWeight(Product product) {
...
}
};
var product = ...; // a Product instance
var weight = weighter.getWeight(product);
Item 19: Variables of Type var Can Be Effectively Final
As a quick reminder:
... starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.
So, variables of typevar
can be effectively final. We can easily see this in the next example.
Avoid:
public interface Weighter {
int getWeight(Product product);
}
// AVOID
int ratio = 5; // this is effectively final
Weighter weighter = new Weighter() {
@Override
public int getWeight(Product product) {
return ratio * ...;
}
};
ratio = 3; // this reassignment will cause error
Prefer:
public interface Weighter {
int getWeight(Product product);
}
// PREFER
var ratio = 5; // this is effectively final
var weighter = new Weighter() {
@Override
public int getWeight(Product product) {
return ratio * ...;
}
};
ratio = 3; // this reassignment will cause error
Item 20: Variables of Type var Can Be final
By default, a local variable of typevar
can be reassigned (except when it is effectively final). But, we can declare it asfinal,
as shown in the following example.
Avoid:
// AVOID
// IT DOESN'T COMPILE
public void discount(int price) {
final int limit = 2000;
final int discount = 5;
if (price > limit) {
discount++; // this reassignment will cause error, which is ok
}
}
Prefer:
// PREFER
// IT DOESN'T COMPILE
public void discount(int price) {
final var limit = 2000;
final var discount = 5;
if (price > limit) {
discount++; // this reassignment will cause error, which is ok
}
}
Item 21: Lambda Expressions and Method References Need Explicit Target-Types
The var type cannot be used since the concrete type cannot be inferred. So, lambdas and method reference initializers are not allowed. This statement is part ofvar
limitations.
The following code will not compile:
// IT DOESN'T COMPILE
// lambda expression needs an explicit target-type
var f = x -> x + 1;
// method reference needs an explicit target-type
var exception = IllegalArgumentException::new;
Instead, use:
// PREFER
Function<Integer, Integer> f = x -> x + 1;
Supplier<IllegalArgumentException> exception = IllegalArgumentException::new;
But, in the lambdas context, Java 11 allows us to usevar
in lambdas parameters. For example, the code below is working in Java 11 (more details in JEP 323 (Local-Variable Syntax for Lambda Parameters)):
// Java 11
(var x, var y) -> x + y
// or
(@Nonnull var x, @Nonnull var y) -> x + y
Item 22: Assigning nulls initializers to the var Type Is Not Allowed
Additionally, a missing initializer is not allowed as well. These are another limitation of the var
type.
The following code will not compile (try to assign null
):
// IT DOESN'T COMPILE
var message = null; // result in an error of type: variable initializer is 'null'
This will not compile as well (missing initializer):
// IT DOESN'T COMPILE
var message; // result in: cannot use 'var' on variable without initializer
...
message = "hello";
Prefer:
// PREFER
String message = null;
// or
String message;
...
message = "hello";
Item 23: the var Type Is Not Allowed on Fields
Thevar
type can be used for local variables, but it cannot be used for fields.
This limitation will lead to compile-time errors here:
// IT DOESN'T COMPILE
public class Product {
private var price; // error: 'var' is not allowed here
private var name; // error: 'var' is not allowed here
...
}
Instead, use:
// PREFER
public class Product {
private int price;
private String name;
...
}
Item 24: the var Type Is Not Allowed in catch Blocks
However, it is allowed in try-with-resources
Catch Block
When a piece of code throws an exception, we have to catch it via explicit type sincevar
is not allowed. This limitation will cause compile-time errors for the following code:
// IT DOESN'T COMPILE
try {
TimeUnit.NANOSECONDS.sleep(5000);
} catch (var ex) {
...
}
Instead, use:
// PREFER
try {
TimeUnit.NANOSECONDS.sleep(5000);
} catch (InterruptedException ex) {
...
}
Try-With-Resources
On the other hand, thevar
type is a very nice fit for try-with-resource. For example:
// explicit type
try (PrintWriter writer = new PrintWriter(new File("welcome.txt"))) {
writer.println("Welcome message");
}
This can be re-written usingvar
:
// using var
try (var writer = new PrintWriter(new File("welcome.txt"))) {
writer.println("Welcome message");
}
Item 25: the var Type Can Be Used With Generic Types, T
Let's suppose that we have the following code:
public <T extends Number> T add(T t) {
T temp = t;
...
return temp;
}
In this case, usingvar
is working as expected, so we can replaceT
withvar,
as follows:
public <T extends Number> T add(T t) {
var temp = t;
...
return temp;
}
Let's see another example wherevar
can be successfully used. for example:
public <T extends Number> T add(T t) {
List<T> numbers = new ArrayList<>();
numbers.add((T) Integer.valueOf(3));
numbers.add((T) Double.valueOf(3.9));
numbers.add(t);
numbers.add("5"); // error: incompatible types: String cannot be converted to T
...
}
It is ok to replaceList <T>
withvar,
as follows:
public <T extends Number> T add(T t) {
var numbers = new ArrayList<T>();
// DON'T DO THIS, DON'T FORGET THE, T
var numbers = new ArrayList<>();
numbers.add((T) Integer.valueOf(3));
numbers.add((T) Double.valueOf(3.9));
numbers.add(t);
numbers.add("5"); // error: incompatible types: String cannot be converted to T
...
}
Item 26: Pay Extra-Attention When Using Wildcards (?), Covariants, and Contravariants With the var Type
Using the ? Wildcard
It is safe to do this:
// explicit type
Class<?> clazz = Integer.class;
// use var
var clazz = Integer.class;
But, don't replace Foo<?>
withvar
just because you have errors in code andvar
makes them disappear by magic! Take the next example, not very inspired, but I hope it points out the main idea. Think about what you actually tried to achieve when you wrote the first snippet of code and act accordingly. Perhaps, you tried to define anArrayList
ofString
and ended up with Collection <?>
.
// explicit type
Collection<?> stuff = new ArrayList<>();
stuff.add("hello"); // compile time error
stuff.add("world"); // compile time error
// use var, this will remove the error, but I don't think that this is
// what you had in mind when you wrote the above code
var stuff = new ArrayList<>();
strings.add("hello"); // no error
strings.add("world"); // no error
Using Covariants (Foo <? extends T>
) and Contravariants (Foo <? super T>
):
We know that we can do this:
// explicit type
Class<? extends Number> intNumber = Integer.class;
Class<? super FilterReader> fileReader = Reader.class;
And if we mistakenly assign a wrong type and recieve compile-time errors, this is exactly what we want:
// IT DOESN'T COMPILE
// error: Class<Reader> cannot be converted to Class<? extends Number>
Class<? extends Number> intNumber = Reader.class;
// error: Class<Integer> cannot be converted to Class<? super FilterReader>
Class<? super FilterReader> fileReader = Integer.class;
But if we use var
:
// using var
var intNumber = Integer.class;
var fileReader = Reader.class;
Then, we can assign any class to these variables, so our bounds/constraints vanish. This is not what we intended to do:
// this will compile just fine
var intNumber = Reader.class;
var fileReader = Integer.class;
Conclusion
Thevar
type is cool, and it will be improved. Keep an eye on JEP 323 (Local-Variable Syntax for Lambda Parameters) and JEP 301 (Enhanced Enums) for more. I hope you enjoyed!
This article is also part of my book Java Coding Problems.
Opinions expressed by DZone contributors are their own.
Comments