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 ;-)