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

Tags: , , , , , , ,

6 responses to “A Lesser-Known Java 8 Feature: Generalized Target-Type Inference”

  1. Martin Andersson says :

    If we ever get “val” or “var”, I will personally go out across the globe and uninstall Java on every computer I find.

    • lukaseder says :

      Good luck, then! What’s wrong with val, though? Any bad experience in Scala or C#?

      • TWiStErRob says :

        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…

        • lukaseder says :

          And how is this…

          // Scalaesque code
          val x = session.getUser();
          doSomething(x);
          

          … fundamentally different from this:

          // Scala or Java code
          doSomething(session.getUser());
          

          … or, let me take things to the extreme. Are you fond of this piece of code, then?

          TransformerFactory tFactory = 
              TransformerFactory.newInstance();            
          Transformer transformer =
              tFactory.newTransformer(new StreamSource(xslFilename));
          
          StreamSource xmlSource = new StreamSource(
              new ByteArrayInputStream(xmlString.getBytes()));
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          transformer.transform(xmlSource, new StreamResult(baos));
          
          formattedOutput = baos.toString();
          

          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…

          • twisterrob says :

            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.

          • lukaseder says :

            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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,964 other followers

%d bloggers like this: