Java is often criticized as being too verbose. One aspect contributing to this characterization is the requirement to specify every type explicitly, which leads to a lot of additional noise.
A new way of declaring local variables with less clutter was given to us with JDK 10 and JEP 286: local variable type inference.
The name itself perfectly describes the feature’s core and its limitations:
- Only local variables are supported
- No methods parameters
- No return types
- No fields
- No lambdas
The Java compiler will automatically detect the correct type for us.
Actually, this isn’t a completely new feature for the JDK.
Inferred types were already supported by the diamond operator
<> (JDK 7).
Lambdas can infer their argument types (JDK 8), too:
This doesn’t mean Java suddenly became dynamically typed. All types will be inferred at compile time, not runtime, providing us with the same safety as before.
How to use var
The basic idea is simple: When we initialize a local variable, we can replace the explicitly stated left-hand type with the newly introduced reversed-type name
All the information for the inferred type must be provided by the initializer (e.g., constructor, literal, method return value):
var not being a keyword, we can also use
final to make the variable not reassignable.
Some restrictions may apply
The type must be inferable at the initialization of a variable, so
null isn’t allowed:
Lambdas can also not be represented with
var, at least not without explicit casting.
Due to lambdas being represented by concrete functional interfaces behind the scenes, the type can’t be inferred without additional context:
By casting it to an explicit interface, we might be able to make the compiler happy.
But this abomination isn’t in the intended spirit of
Reading Vs. Writing Code
Our code will be read way more often than it’ll be written.
While writing code, all the context is still right there. Whenever someone reads our code, even ourselves, we need to be able to reason with it.
“Programs are meant to be read by humans and only incidentally for computers to execute.”
— Donald Knuth
The verbosity of Java can be a mental burden by confronting us with more code than is actually needed to comprehend it.
var can help to reduce this additional, often redundant information. But not all code removed by it might be redundant.
It might remove any indicators of the bigger context.
To reason with code means understanding its context and impact. If we remove the explicit type information, we need to make sure the context can be deducted in other ways. Just because the compiler might infer the correct types doesn’t automatically mean we, as humans, can do it.
We should always be able to comprehend code in its local scope without knowing the complete bigger picture surrounding it.
This is why not every local variable declaration should be using
Excellent use cases for
var are constructs already containing explicit context: constructors, literals, and static factory methods.
Constructors are made up of their type so not much more information is possible:
No additional context needed.
Literals can provide all the context needed to infer the correct type if we comply with their special notations:
If we don’t comply, literals could be inferred as another type. In practice, they still might work due to implicit casting. But the actual type would be wrong:
short don’t have special indicators, so they always will be inferred to
Static factory methods
Many types contain static factory methods, providing just as much information as a constructor, either by the class name or the factory-method name:
Another aspect of
var is the ability to replace unnecessary information.
Local variables are an easy and cheap way of storing intermediate values in a narrow scope. For understanding the context, the actual type these variables are might not be as crucial as its name and its surroundings:
By using better variable names, we can rely on
var and still grasp the context:
As seen before, loops can be simplified with
Usually the surrounding context provides enough information, so we don’t need explicit types anymore:
try-with-resources block is a very verbose construct.
But thanks to
var, we can make them more concise:
Generic type declarations, especially, can be a mouthful.
Iterator can be as complicated as this:
By knowing what type the
Map is, we don’t need to bother with the explicit types of the iterator or the entry:
This is much easier on the eyes and still as understandable as before.
var and destroying valuable bits of information, there are multiple caveats to be aware of.
As mentioned before, the diamond operator
<> already provides us with type inference.
The compiler infers the right-hand type based on the left-hand declaration:
var, we no longer have this information and must provide it ourselves in the initializer:
That doesn’t mean we can’t use the diamond operator at all, though. If the initializer provides enough information due to other circumstances, the compiler can infer the correct type:
Interface vs. concrete types
Usually we code against interfaces and not concrete implementations:
This abstraction provides us with a lot of flexibility for future changes. But with type inference we only get the type of the initializer, not its interface:
By being only available for local variables, this shouldn’t impose much of a problem. We still should code against abstractions for public interfaces. But in a local scope, it doesn’t matter as much.
As mentioned before, literals need additional context via type-specific indicators to be correctly inferred:
TYPE | INDICATOR ------- | ------------------------------------ String | double-quoted char | single-quoted int | whole number long | whole number ending with "L/l" float | decimal number ending with "f/F" double | decimal number, optional "d/D" byte | no indicator, always inferred to int short | no indicator, always inferred to int
Better type inference is a great addition to Java. Static compile-time type safety paired with less typing and more concise code is a win-win.
Omitting the explicit type reduces clutter, as long as we have other bits of information to deduce the context.
But there’s more to it than just replacing an existing type declaration with
Code must be reasoned with thanks to its surrounding context.
We can improve this context by choosing better variable names and narrowing the scope of intermediate values.
If understandability is still impaired by using type inference, it can be an indicator for deeper structural problems, and
var might not be the best way to go.
- JEP-286 (OpenJDK)
- Local Variable Type Inference: FAQ (OpenJDK)
- Local Variable Type Inference: Style Guidelines (OpenJDK)
- Var with Style: Local Variable Type Inference in Java 10 by Stuart Marks (YouTube)