When hearing the words “type inference”, most developers probably think of local-variable type inference that arrived in Java 10. But type inference in Java is much older than that. Some sort of it existed even in Java 6, where compilers could infer type parameters of generic constructors! For sure the most famous type inference example is the diamond operator in Java 7. Since the beginning, mere reduction of number of characters used in source code went hand in hand with type inference. But is that its biggest advantage?

A cursory view on type inference

It’s not a secret that Java is (still) more verbose comparing to many other programming languages. When local-variable type inference arrived in Java 10, a lot of people were seduced by reducing number of characters they have to type (and, subsequently, read) and touted that as its biggest advantage. Classic example of such approach looks like this:

1// Java 1.5
2Map<String, Integer> wordFrequency = new HashMap<String, Integer>();
3// Java 7
4Map<String, Integer> wordFrequency = new HashMap<>();
5// Java 10
6var wordFrequency = new HashMap<String, Integer>();

Yes, this variable’s declaration line length dropped from 68 characters in Java 1.5 to 53 in Java 7 to 51 in Java 10. While this is a nice consequence of using local-variable type inference, shortening lines shouldn’t be your main priority. Otherwise, what do you think about this snippet:

1var f = new HashMap<String, Integer>();

It’s only 30 characters long so it’s shorter than all previous snippets and therefore must be better than them, right? Well… no. It’s worse because it lacks readability. When you see wordFrequency as a variable name, you can be pretty sure that it holds a frequency or a number of occurrences for each word. When you see f… well it can be anything. Typically, any code is written once or twice but read many times (and even by different people). This makes readability more important than line length and, as a consequence, you shouldn’t use var only as a means to minimize the amount of characters you type.

With that out of the way, here are 3 reasons why (and how) senior developers really use type inference.

Use it only when the type is easy to infer for humans

“Whoa!”, I hear you say already. “For humans? Really? Shouldn’t the compiler be able to infer the type?” Yes, it should, but that’s not enough. People read the code in different contexts using different tools - while developing we use an IDE, while reviewing other people’s code in a code review system that is nowadays often a webapp, while resolving merge conflicts we might use a specialised merge tool… Making your reader guess the type of a variable is simply bad. Use a simple rule of thumb here: if there is a new operator or a literal value on the right-hand side then use var, otherwise use a proper type.

Here are some examples of usages that follow this rule:

1var count = 42; // literal
2var blogName = "TheJavaGuy"; // literal
3var lottoNumbers = new int[] {16, 25, 27, 41, 45}; // new operator

and one example that doesn’t:

1var value = resolver.getAttributeValue(); // difficult to guess what is a resolver and what it returns

In a nutshell, don’t make people guess types.

Emphasise variable names, not types

Even in justified cases of using var we lose a bit of information about the type of a variable and there’s no way around it. The type is now implicit where before it was explicit. This means you have to be extra careful with variable names. As an old saying goes, there are only two difficult problems in computer science: naming, cache invalidation, and off-by-one errors. By using var, the first one becomes even more important.

You’ve probably seen something like this in a lot of code bases:

1FileReader fr = new FileReader("results.txt");
2BufferedReader br = new BufferedReader(fr);

Variable names such as fr and br are very generic and ambiguous. Such names don’t tell anything about variable’s content nor the role it plays in an application. While it’s true that we can blindly replace explicit types with var, in this case we can do better by simply adding more domain-related context to variable names. My favourite rule of thumb here is that the best variable names are of the form noun, adjectiveNoun, or even determinerAdjectiveNoun or quantifierAdjectiveNoun, like lottoNumbers, blogName, latestResults, highScore. Following this rule, previous example could become var electionResults = new BufferedReader(new FileReader("results.txt"));.

As with almost any rule there is an edge case. What do you think about this example?

1// variation 1
2for (PurchaseOrder order : customer.getPurchaseOrders()) {...}
3// variation 2
4for (var order : customer.getPurchaseOrders()) {...}
5// variation 3
6for (var purchaseOrder : customer.getPurchaseOrders()) {...}

It sits somewhere in between “don’t make people guess types” and “emphasise variable names”. If we use var here, former rule is violated because there’s no new operator or a literal on the right. On the other hand, purchaseOrder is quite clear variable name (maybe we could even call it currentPurchaseOrder). Personally I still like variation 1 the most, but in no way is variation 3 a mistake.

In a nutshell, focus on variable names and meaning rather than their technical types.

Remove nasty generics with wildcards

Generics have been ubiquitous in Java for years and you probably encounter them daily. And often is the case, especially when dealing with collections, to work with bounded wildcards in generics. They can tighten or relax restrictions on generic type but they come with a price, and that price is verbosity. Perhaps the most typical example thereof is iterating over Map elements:

1for (Iterator<? extends Map.Entry<? extends String, ? extends Number>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
2    Map.Entry<? extends String, ? extends Number> entry = iterator.next();
3    // ...
4}

The previous snippet heavily uses upper bounded wildcards to relax restrictions on map entries. However the price we pay for that is 65 characters long type declaration of iterator!

After replacing with var a lot of the noise is removed:

1for (var iterator = map.entrySet().iterator(); iterator.hasNext();) {
2    var entry = iterator.next();
3    // ...
4}

Do you like terseness of the result? I certainly do! Please remember previous two rules and don’t just blindly use var. For example, you might opt for more descriptive name instead of an entry but that depends on your concrete use case and what you want to achieve by iterating map entries.

In a nutshell, the more wildcarded your generics are the larger benefits you’ll see from var.

Official JEP specification

If you want to dive deeply into specification of this feature, you may find it in JEP 286.

Dear fellow developer, thank you for reading this article about 3 ways to write higher quality software by using type inference. Until next time, TheJavaGuy saluts you!