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 ;-)
Like this:
Like Loading...
Published by lukaseder
I made jOOQ
View all posts by lukaseder
If we ever get “val” or “var”, I will personally go out across the globe and uninstall Java on every computer I find.
Good luck, then! What’s wrong with val, though? Any bad experience in Scala or C#?
I agree with Martin.
You lose type information, it’s strongly typed, but only the compiler knows… it’ll be like in C++ when you miss a & or *.
Inference is good, as long as humans are able to infer everything correctly.
If you see “val x = session.getUser()”, what’s x’s type? int? String? User? Object? you don’t see that unless you know what getUser()’s exact signature is (which can be invisible again because the getUser method’s return type may be inferred from the return expression / scala), now let’s check “String x = session.getUser()”, I think I don’t need to explain much here…
And how is this…
… fundamentally different from this:
… or, let me take things to the extreme. Are you fond of this piece of code, then?
While you cannot get much more explicit than the above, I do wonder if any of those noisy types really added much value to the programme :-)
I personally like Xtend’s approach, where explicit target typing is optional in those rare cases where it really adds value. In times of IDEs and auto-completion, and type inspection, I think that the few ambiguous cases can be easily introspected using tooling…
Hmm, that makes sense. Actually I never used Scala myself yet, just been to a conference. The example you gave is the good reason why they added it, and I vaguely remember that Scala can do the “optionality” with val:String x or similar, but I’m not sure. Sometimes it’s good to be able to make the type explicit. Probably it’s a matter of style and whether programmers are using it according to its original intent and anything can be mis/overused.
In my examples I may have cheated a little because if you know that session is a library object the getUser is probably Object; otherwise it would be getUser, getUserName or getUserID.
Note that this isn’t about Scala in particular, but about pretty much all modern languages, including C# where “val has hit the Enterprise” and was adopted without great pain.
val
really shines once you use generics, and possibly types involving complex generic intersection types that cannot even be assigned at all. This can happen with Java. Some expressions can only be assigned to raw types.As for var/val you can even use it in Java 5 with help of Xtend.Its part of EMF ecosystem, but I don’t see why it can’t be used in a bit broader use.
There are a lot of reasons why you shouldn’t “just” use Xtend in your application. Xtend is a very useful language for templating, and it certainly has a lot of nice features that slightly improve Java. BUT:
Now, as always, of course, YMMV. I just don’t think that people should add new languages to the stack prematurely, and most certainly, they shouldn’t do this just for the benefit of being able to write var/val.