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

8 thoughts on “A Lesser-Known Java 8 Feature: Generalized Target-Type Inference

      1. 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…

        1. 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…

          1. 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.

            1. 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.

  1. 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.

    1. 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:

      • It still largely depends on Eclipse.
      • It has had a couple of backwards-incompatible upgrades in the past. This can be a killer for any larger code base.
      • I’ve experienced the two-phase compilation to be rather slow
      • I’ve found the cognitive dissonance between Java / Xtend syntax to be an issue many times

      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.

Leave a Reply