Local Variable Type Inference in Java 10
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.
Table of Contents
General Concept
The name itself perfectly describes the feature’s core and its limitations:
Local variable
- Only local variables are supported
- No methods parameters
- No return types
- No fields
- No lambdas
Type inference
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 var
instead.
All the information for the inferred type must be provided by the initializer (e.g., constructor, literal, method return value):
With 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 var
.
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 var
.
Explicit Context
Excellent use cases for var
are constructs already containing explicit context: constructors, literals, and static factory methods.
Constructors
Constructors are made up of their type so not much more information is possible:
No additional context needed.
Literals
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:
The literals byte
and short
don’t have special indicators, so they always will be inferred to int
instead.
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:
Implicit Context
Another aspect of var
is the ability to replace unnecessary information.
Intermediate values
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:
Loops
As seen before, loops can be simplified with var
.
Usually the surrounding context provides enough information, so we don’t need explicit types anymore:
try-with-resources
A try-with-resources
block is a very verbose construct.
But thanks to var
, we can make them more concise:
Generics
Generic type declarations, especially, can be a mouthful.
A simple 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.
Caveats
Besides overusing var
and destroying valuable bits of information, there are multiple caveats to be aware of.
Diamond operator
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:
With 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.
Literals
As mentioned before, literals need additional context via type-specific indicators to be correctly inferred:
Conclusion
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 var
indiscriminately.
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.
Resources
- 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)