In Java Futures at QCon New York, Java Language Architect Brian Goetz took us on a whirlwind tour of some recent and future features in the Java Language. In this article, he dives into Local Variable Type Inference.
- Java SE 10 (March 2018) introduced type inference for local variables, one of the most frequently requested features for Java in recent.
- Type inference is a technique used by statically typed languages, where the types of variables may be inferred from context by the compiler.
- Type inference in Java is local; the scope over which constraints are gathered and solved is restricted to a narrow portion of the program, such as a single expression or statement.
- Stuart Marks, of the Java Libraries team, has compiled a useful style guide and FAQ to help understand the trade-offs surrounding type inference for locals.
- Correctly used, type inference can make your code both more concise and more readable.
Java SE 10 (March 2018) introduced type inference for local variables. Previously, declaring a local variable required a manifest (explicit) type declaration. Now, type inference empowers the compiler to choose the static type of the variable, based on the type of its initializer:
var names = new ArrayList<String>();
In this simple example, the variable
names will have the type
Type Inference in Java
Type inference is a technique used by statically typed languages, where the types of variables may be inferred from context by the compiler. Languages vary in their use and interpretation of type inference. Type inference generally provides the programmer with an option, not an obligation; we are free to choose between manifest and inferred types, and we should make this choice responsibly, using type inference where it enhances readability and avoiding it where it might create confusion.
Type names in Java can be long, either because the class name itself is long, has complex generic type parameters, or both. It is a general fact of programming languages that the more interesting your types are, the less fun they are to write down — which is why languages with more sophisticated type systems tend to lean more heavily on type inference.
Java started with a limited form of type inference in Java 5, and its scope has steadily expanded over the years. In Java 5, when generic methods were introduced, we also introduced the ability to infer the generic type parameters at the use site; we typically say:
List<String> list = Collection.emptyList();
rather than providing explicit type witnesses:
List<String> list = Collection.<String>emptyList();
In fact, the inferred form is so common, that some Java developers have never even seen the explicit form!
In Java 7, we extended the scope of type inference to infer type parameters of generic constructor invocations (also known as “diamond”); we can say
List<String> list = new ArrayList<>();
as a shorthand for the more explicit
List<String> list = new ArrayList<String>();
In Java 8, when we introduced lambda expressions, we also introduced the ability to infer the types of the formal parameters of lambda expressions. So, we could say:
list.forEach(s -> System.out.println(s))
as a shorthand for the more explicit
list.forEach((String s) -> System.out.println(s))
And, in Java 10, we further extended type inference to the declaration of local variables.
Some developers might think routine use of inferred types is better, because it results in a more concise program; others might think it’s worse because it removes potentially useful information from view. But, both of these views are simplistic. Sometimes, the information that would be inferred is merely clutter that would otherwise just get in the way (no one complains that we routinely use type inference for generic type parameters), and in these cases, type inference makes our code more readable. In other cases, the type information provides vital clues about what is going on, or reflects creative choices by the developer; in these cases, it is better to stick with manifest types.
While we’ve expanded the scope of type inference over the years, one design principle we’ve followed is only using type inference for implementation details, not declaration of API elements; the types of fields, method parameters, and method returns must always be manifestly typed, because we don’t want API contracts subtly changing based on changes in the implementation. But, inside the implementation of method bodies, it’s reasonable to have more latitude to make choices on the basis of what is more readable.
Geïnteresseerd in de rest van het artikel?Lees hier het hele artikel