; last updated - 9 minutes read

My previous post raved about the simplicity type inference is going to bring to Java. I love the idea. Java is such a verbose, ceremonious language. Little wonder so many developers prefer dynamically typed languages, which seem to be so much simpler to use until you write a huge enterprise application. Java's proposed type inference is strongly and statically typed, so it's going to make life simpler without introducing problems. JEP 286 is good news, indeed!

Type infererence obfuscates types

A fine mess it is, Dave Brosius answered. Consider this code. It's wrong, but can you spot the error?

public boolean foo(Set ids) { val myId = getId(); return ids.contains(myId); }

In fact, I couldn't until Dave helped me, providing the solution: getId() returns a String, so the contains method will never find the id.

Well. On the one hand Dave is right: the val keyword obfuscates the fact that we're talking about a String, so it's a potential source of confusion. You'll spot the bug immediately with the Java 8 version of the code:

public boolean foo(Set ids) { String myId = getId(); return ids.contains(myId); }

This source code clearly reveals that we're comparing Strings with numbers, which is unlikely to work (at least in the Java language). Readability matters!

On the other hand, the example is all wrong. It's deliberately misleading. I, for one, was an easy victim because I associated the ID of the code snippet with a database primary key, and my primary keys are always numeric. I prefer to have them generated by a sequence of the database.

Several years ago many developers adopted the UUID as an alternative synthetic primary key. From this perspective, I shouldn't have been surprised by the fact the getId() returns a String. But then, why don't we call the method getUUID()? Readability matters!

Another interesting aspect is that JEP 286 has been carefully designed to minimize readability problems. In my eyes it's too conservative, but Dave sought and found a weak spot of the Java API that doesn't work well with type inference. Even though Set is a generic type, the contains() method doesn't care about the type. It simply accepts arbitrary Objects as parameter, allowing us to try to search for a String, even though it should be clear from the context that there are no String in the set.

My personal summary: Granted, you have a point, Dave. Type inference might obfuscate the type, and that's a bad thing. But if used carefully, this shouldn't be too much of a problem. I can't remember someone complaining about type inference with Scala, Kotlin or Ceylon. Well, maybe I've heard people complain about Scala type inference. Scala seems to attract very clever guys, who love to write their programs in a very, say, academic way. Scala's type inference is very sophisticated. Sometimes this leads to very sophisticated Scala programs, and that's one of the key aspects why Scala never gained traction. The Java enhancement proposal is much more conservative. Probably exactly because of the Scala experience.

By the way, as Phil Webb pointed out, using the val keyword isn't the real source of the problem. Using the "inline" refactoring causes the same obfuscation problem:

public boolean foo(Set ids) { return ids.contains(getId()); }

IDEs to the rescue

When I published this article on Reddit, the first comment pointed out that sooner or later IDEs are able to display the type of the local variable, even if it is defined as a var or val. That doesn't solve every readability problem because most likely the type of the variable is displayed in a tooltip only. In other words: most of the time it is invisible. But certainly, even that tooltip will help a lot. In fact, the tooltip is an excellent example of my idea: developers aren't bothered with technical details all the time, but they can easily look them up when they need them.

Another example of obfuscation

Mark Struberg contributed another potential example of trouble introduced by var and val:

val myValue = a().b().c().d();

Which type does myValue have? What happens when the return type of d() changes? Plus, in real life code, the chain of calls may not be quite as simple. It may be hidden among hundreds of lines, each looking deceptively innocent.

Granted, that's the sort of things that are going to happen, and it'll be the source of countless overtime hours. But the real question is: which is worse? Actually, we could even run an experiment to decide on the question of hours of overtime. Does type inference increase the number of extra hours, or does it increase them?

Lessons learned from other languages

Most modern programming languages make use of type inference. There's a lot of discussions whether static or dynamic typing is useful, but so far, I've heard little complains about type inference in strongly and statically typed languages like Scala, C#, Ceylon or Kotlin. I'm skeptic about dynamic typing, but the current JEP 286 is limited to strong and static typing, so judging from the experience of the communities of other programming languages, I suppose adding limited type inference to Java is going to be a success story.

Mind you: the examples of Dave and Mark have been carefully designed to expose the weaknesses of the JEP. That's good because they made us aware of a problem we might have missed otherwise. But then, why don't we rely on the common sense of the programmers? Usually, they know how much abstraction they and their working mates can cope with. Adding type inference to Java doesn't forbid us to use explicit types. So let's give it a try, and use techniques like code review or pair programming to make sure our code doesn't get too sophisticated!

Benefits of type inference

During our discussion on Twitter, Phil Webb exclaimed he's sometimes afraid of extracting a term into a local variable because of the scary type declaration. Well, this rings a bell with me. I often use this particular refactoring to make the code more readable. Extracting a term to a local variable allows us to assign an expressive variable name to the term, and modern JVMs are clever enough to inline the variable automatically again. But if this local variable is drowned by a verbose type declaration, little is won. In this particular example, using val or var might improve readability a lot. More often then not, we aren't interested in the type of the variable (that's a technical issue), but we are interested in the role the variable plays (which is a business issue).

That's the general pattern why type inference may make the Java language more useful. It gives you an option to hide technical stuff when its irrelevant for the reader. As they say, code that's not there is code you don't have to understand. It goes without saying that there are corner cases. Sometimes code is so clever it's so compact it's difficult to decipher. It's like learning latin. At school, we used to ponder half an hour just to decipher a single sentence. Obviously, that's the wrong approach. But if used wisely, type inference may add to the readability of the source code.

Like I said: Readability matters!

JEP 286 addresses Type Inference in a very conservative way

Given that Brian Goetz (or whoever is the author of this particular line) calls type inference a "non-controversial feature", there's quite a lot of controversy about the topic. But if you're afraid of type inference, read the JEP. It's a fast read - maybe five to ten minutes - and it's going to make you rest assured. The authors have limited the scope of type inference considerably.

Target typing vs. left-hand-side type inference

In part, that's due to target typing. The adoption of Lambda Expressions brought target typing to the Java ecosystem. This is also known as right-hand-side type inference because it adds type inference to the right-hand side of assignments. JEP 286 adds type inference to the left-hand side of the assignment operator. Obviously, it's difficult to have both. This might turn out as a problem to Java programmers.

It's your responsibility!

But then, with power comes responsibility. Type inference doesn't mean you are forced to convert every type declaration to a var or val declaration. It's up to you. If you think var is useful, use it. Otherwise, use the traditional approach, which may be as verbose as

Map> map = new HashMap>();

In my eyes, this is a scary example: you don't know anything about the business value the attributes add to your program. You only know it's a map, and reading the type declaration, you learn twice it's a map of maps. Probably it's better to use val and an expressive identifier:

val interestRateTableByDate = new HashMap>();

It's still a difficult line, but if you're working in the finance industry, you might at least guess that this is a collection of tables of interest rates (which are different depending how much money you invest). Interest rates are volatile, so you need to add another dimension - the date - to determine the interest rate.

Granted, you can also do this without val, but then the variable name sort of drowns in technical stuff:

Map> interestRateTableByDate = new HashMap>();


Dig deeper

JEP286 (adding local variable type inference to Java)

My previous article on the proposal to add local variable type inference to Java

Lukas Eder on local variable type inference

Pros and cons of JEP 286 by Roy van Rijn


Comments