A Lesser-Known Java 8 Feature: Generalized Target-Type Inference
Going through the list of Java 8 features, Generalized Target-Type Inference struck me as a particularly interesting, lesser-known gem. It looks as though the Java language designers will ease some of the pain that we’ve been having with generics in the past (Java 5-7). Let’s have a look at their example:
class List<E> { static <Z> List<Z> nil() {..} static <Z> List<Z> cons(Z head, List<Z> tail) {..} E head() {..} }
Given the above example, the JEP 101 feature claims that it would be nice to be able to write:
// This: List.cons(42, List.nil()); String s = List.nil().head(); // ... instead of this: List.cons(42, List.<Integer>nil()); String s = List.<String>nil().head();
Being a fluent API designer myself, I was thrilled to see that such an improvement is on the roadmap, particularly the latter. What’s so exciting about these changes? Let me comment on that more in detail:
// In addition to inferring generic types from // assignments List<String> l = List.nil(); // ... it would be nice for the compiler to be able // to infer types from method argument types List.cons(42, List.nil()); // ... or from "subsequent" method calls String s = List.nil().head();
So in the last example where methods are chained, the type inference would be delayed until the whole assignment expression has been evaluated. From the left-hand side of the assignment, the compiler could infer that <Z>
binds to String
on the head()
call. This information could then be used again to infer that <Z>
binds again to String
on the nil()
call.
Sounds like a lot of trickery to me, as the nil()
call’s AST evaluations would need to be delayed until a “dependent” sub-AST is evaluated. Is that a good idea?
Yes, this is so awesome!
… you may think. Because a fluent API like jOOQ or the Streams API could be designed in a much much more fluent style, delaying type inference until the end of the call chain.
So I downloaded the latest evaluation distribution of the JDK 8 to test this with the following program:
public class InferenceTest { public static void main(String[] args) { List<String> ls = List.nil(); List.cons(42, List.nil()); String s = List.nil().head(); } }
I compiled this and I got:
C:\Users\Lukas\java8>javac InferenceTest.java InferenceTest.java:5: error: incompatible types: Object cannot be converted to String String s = List.nil().head(); ^ 1 error
So, the type inference based on the method argument type is implemented (and thus, compiles), but not the type inference for chained method calls. I searched the internet for an explanation and found this Stack Overflow question linking to this interesting thread on the lambda-dev mailing list.
It appears that the Java type system has become quite complex. Too complex to implement such crazy type inference stuff. But still, a slight improvement that will be greatly valued when writing every day Java 8 code.
And maybe, in Java 9, we’ll get val
and var
, like everyone else!
Hello Lukas,
thanks for the info about the limitation of type inference with chained method calls in Java 8. Perhaps I will write about this on my Java-Website in Germany. I will then link to your website as ressource.
Best regards.
Chris