Java 10’s new Local-Variable Type Inference

News could hardly get more exciting than this, for a programming language aficionado! There is now a JEP 286 for Local-Variable Type Inference with status “Candidate”. And a request for feedback by Brian Goetz, which I would love to invite you to participate in: http://mail.openjdk.java.net/pipermail/platform-jep-discuss/2016-March/000037.html Please do so, the survey remains open only from March 9 to March 16! This is not a feature that will be implemented. It might be implemented. Hence, there is no specific Java version yet, which is why I name the Java version “A” (for Awesome).

What is local-variable type inference and why is it good?

Let’s have a look at a feature that various other languages have had for quite a while. In this blog post, I’d like to discuss the general idea, not the possibly specific implementation that might be planned for Java, as that would be too early, and I certainly don’t have the big picture of how this fits into Java. In Java, as well as in some other languages, types are always declared explicitly and verbosely. For instance, you write things like:

// Java 5 and 6
List<String> list = new ArrayList<String>();

// Java 7
List<String> list = new ArrayList<>();

Notice how in Java 7, some syntax sugar was added via the useful diamond operator <>. It helps removing unnecessary redundancy in the Java way, i.e. by applying “target-typing”, which means the type is defined by the “target”. Possible targets are:
  • Local variable declarations
  • Method arguments (both from the outside and from the inside of the method)
  • Class members
Since in many cases, the target type MUST be declared explicitly (method arguments, class members), Java’s approach makes a lot of sense. In the case of local variables, however, the target type doesn’t really need to be declared. Since the type definition is bound to a very local scope, from which it cannot escape, it may well be inferred by the compiler without the source code ever being explicit about it, from the “source type”. This means, we will be able to do things like:

// Java 10 as suggested in the JEP

// infers ArrayList<String>
var list = new ArrayList<String>();

// infers Stream<String>
val stream = list.stream();

In the above example var stands for a mutable (non-final) local variable, whereas val stands for an immutable (final) local variable. Notice how the type of list was never really needed, just as when we write the following, where the type is already inferred today:

stream = new ArrayList<String>().stream();

This will work no different from lambda expressions, where we already have this kind of type inference in Java 8:

List<String> list = new ArrayList<>();

// infers String
list.forEach(s -> {
    System.out.println(s);
};

Think of lambda arguments as local variables. An alternative syntax for such a lambda expression might have been:

List<String> list = new ArrayList<>();

// infers String
list.forEach((val s) -> {
    System.out.println(s);
};

Other languages have this, but is it good?

Among these other languages: C# and Scala and JavaScript, if you will ;). YAGNI is probably an common reaction to this feature. For most people, it’s mere convenience to be able not to type all types all the time. Some people might prefer to see the type explicitly written down, when reading code. Especially, when you have a complex Java 8 Stream processing pipeline, it can get hard to track all the types that are inferred along the way. An example of this can be seen in our article about jOOλ’s window function support:

BigDecimal currentBalance = new BigDecimal("19985.81");
 
Seq.of(
    tuple(9997, "2014-03-18", new BigDecimal("99.17")),
    tuple(9981, "2014-03-16", new BigDecimal("71.44")),
    tuple(9979, "2014-03-16", new BigDecimal("-94.60")),
    tuple(9977, "2014-03-16", new BigDecimal("-6.96")),
    tuple(9971, "2014-03-15", new BigDecimal("-65.95")))
.window(Comparator
    .comparing((Tuple3<Integer, String, BigDecimal> t) 
        -> t.v1, reverseOrder())
    .thenComparing(t -> t.v2), Long.MIN_VALUE, -1)
.map(w -> w.value().concat(
     currentBalance.subtract(w.sum(t -> t.v3)
                              .orElse(BigDecimal.ZERO))
));

The above implements a running total calculation that yields:
+------+------------+--------+----------+
|   v0 | v1         |     v2 |       v3 |
+------+------------+--------+----------+
| 9997 | 2014-03-18 |  99.17 | 19985.81 |
| 9981 | 2014-03-16 |  71.44 | 19886.64 |
| 9979 | 2014-03-16 | -94.60 | 19815.20 |
| 9977 | 2014-03-16 |  -6.96 | 19909.80 |
| 9971 | 2014-03-15 | -65.95 | 19916.76 |
+------+------------+--------+----------+
While the Tuple3 type needs to be declared because of the existing Java 8’s limited type inference capabilities (see also this article on generalized target type inference), are you able to track all the other types? Can you easily predict the result? Some people prefer the short style, others claim:
On the other hand, do you like to manually write down a type like Tuple3<Integer, String, BigDecimal>? Or, when working with jOOQ, which of the following versions of the same code do you prefer?

// Explicit typing
// ----------------------------------------
for (Record3<String, Integer, Date> record : ctx
    .select(BOOK.TITLE, BOOK.ID, BOOK.MODIFIED_AT)
    .from(BOOK)
    .where(TITLE.like("A%"))
) {
    // Do things with record
    String title = record.value1();
}

// "Don't care" typing
// ----------------------------------------
for (Record record : ctx
    .select(BOOK.TITLE, BOOK.ID, BOOK.MODIFIED_AT)
    .from(BOOK)
    .where(TITLE.like("A%"))
) {
    // Do things with record
    String title = record.getValue(0, String.class);
}

// Implicit typing
// ----------------------------------------
for (val record : ctx
    .select(BOOK.TITLE, BOOK.ID, BOOK.MODIFIED_AT)
    .from(BOOK)
    .where(TITLE.like("A%"))
) {
    // Do things with record
    String title = record.value1();
}

I’m sure that few of you would really like to explicitly write down the whole generic type, but if your compiler can still remember the thing, that would be awesome, wouldn’t it? And it’s an opt-in feature. You can always revert to explicit type declarations.

Edge-cases with use-site variance

There are some things that are not possible without this kind of type inference, and they’re related to use-site variance and the specifics of generics as implemented in Java. With use-site variance and wild cards, it is possible to construct “dangerous” types that cannot be assigned to anything because they’re undecidable. For details, please read Ross Tate’s paper on Taming Wildcards in Java’s Type System. Use-site variance is also a pain when exposed from method return types, as can be seen in some libraries that either:
  • Didn’t care about this pain they’re inflicting on their users
  • Didn’t find a better solution as Java doesn’t have declaration-site variance
  • Were oblivious to this issue
An example:

interface Node {
    void add(List<? extends Node> children);
    List<? extends Node> children();
}

Imagine a tree data structure library, where tree nodes return lists of their children. A technically correct children type would be List<? extends Node> because the children are Node subtypes, and it is perfectly OK to use a Node subtype list. Accepting this type in the add() method is great from an API design perspective. It allows people to add a List<LeafNode>, for instance. Returning it from children() is horrible, though, because the only options are now:

// Raw type. meh
List children = parent.children();

// Wild card. meh
List<?> children = parent.children();

// Full type declaration. Yuk
List<? extends Node> children = parent.children();

With JEP 286, we might be able to work around all of this and have this nice fourth option:

// Awesome. The compiler knows it's 
// List<? extends Node>
val children = parent.children();

Conclusion

Local Variable Type Inference is a hot topic. It’s entirely optional, we don’t need it. But it makes a lot of things much much easier, especially when working with tons of generics. We’ve seen that type inference is a killer feature when working with lambda expressions and complex Java 8 Stream transformations. Sure, it will be harder to track all the types across a long statement, but at the same time, if those types were spelled out, it would make the statement very unreadable (and often also very hard to write). Type inference helps make developers more productive without giving up on type safety. It actually encourages type safety, because API designers are now less reluctant to expose complex generic types to their users, as users can use these types more easily (see again the jOOQ example). In fact, this feature is already present in Java in various situations, just not when assigning a value to a local variable, giving it a name. Whatever your opinion is: Do make sure to share it to the community and answer this survey: http://mail.openjdk.java.net/pipermail/platform-jep-discuss/2016-March/000037.html Looking forward to Java 10.

8 thoughts on “Java 10’s new Local-Variable Type Inference

  1. you meant «var» for JEP286 maybe? anyway I like how it’s used in Scala, if I remember well: «var» for variables and «val» for finals (but it would be another syntactic sugar in order to get short «final var»). It would be a nice feature, maybe in Java10…

    I don’t understand the comment by Steve Chaloner, I always thought if there’s an well-known, readable and effective syntactic sugar (trivial example: «expr» ? «res1» : «res2») it’s always reasonable to use it! Otherwise he could say the same for lambda-expressions in Java: «I always declare my lambda class function»… jeez ;)

    1. The var vs val vs let vs auto discussion is still ongoing. Of course, this is the least important part of the discussion, but the one everyone can bikeshed about :)

      If they had asked me, I’d be happy with just being able to type final, leaving away the type. That would prevent the keyword discussion / tricks.

      Yes, nay-sayers to this features also often forget the ability to chain methods. Like "abc".substring(1, 3).replace("a","x").length(). Apparently, they have always feverishly assigned every method call to a new, explicitly typed, local variable since Java 1.0

      1. It is not exactly the same – such chaining mostly occurs when you use so-called “fluent API”, in which case all calls return not only the same type, but even the same object, so you have nothing to guess.

        In more rare cases objects differ but their types do not (like in your example).

        Practically each other usage of such chaining represents a design bug as it increases a risk of NPE amd jeopardizes Loose Coupling and Law of Demeter principles.

        1. but even the same object

          So, what’s Stream, according to you? It doesn’t always return the same type (Stream, IntStream), and it mostly doesn’t return the same object…

          Practically each other usage of such chaining represents a design bug as it increases a risk of NPE amd jeopardizes Loose Coupling and Law of Demeter principles.

          Interesting. Can you show an example?

  2. it’s the same for me as well (val, let, var, etc…), just bringing the feature to Java it would be nice :)

    well I think it depends on what their attitudes on coding are, although if you’re coding in Scala, why do you complain about val/var if it’s all about it?

    what’s you thoughts about «polymorphic this typing» as return type then?

    1. I must admit, I don’t fully follow you there. I’m not coding in Scala. And what do you mean by “polymorphic this typing”?

      1. sorry it was a continuation of my thought about Steve Chaloner’s comment, never mind :)

        «polymorphic this typing» it was introduced in Typescript 1.7, in order to simplify Fluent-API building, let me show you the concept in Java:

        interface FluentlyFirst {
          default FluentlyFirst doSomething() { ...; return this; }
        }
        
        interface FluentlySecond extends FluentlyFirst {
          default FluentlySecond doSomethingElse() { ...; return this; }
        }
        
        class Fluental implements FluentlySecond {
          ...
        }
        
        var fluent = new Fluental(); // JEP286!
        

        as you can imagine you can’t do something like this fluent.doSomething().doSomethingElse().

        However with «polymorphic this typing» you would be able to do declare interfaces in the following way:

        interface FluentlyFirst {
          default this doSomething() { ...; return this; }
        }
        
        interface FluentlySecond extends FluentlyFirst {
          default this doSomethingElse() { ...; return this; }
        }
        

        (in this way I think it would be also possible to make the return implicit)

        after that you can add all the interfaces you need to make your Fluent-API cool. In my mind I also imagined to get rid of the «void» type, but this is a huge topic I believe.
        I like such approach, but I dunno if it breaks the Law-of-Demeter.

        1. Interesting. I didn’t know TypeScript had such a feature.

          Well, frankly… If designing a fluent API is your goal (or a full internal DSL), then you should think really hard about types anyway. I don’t see how such an API designer would want to omit explicit types to stay in full control. And it isn’t that hard to do. See my old post:
          https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course

          Java implemented recursive generics for similar reasons, and it is one of the worst and most complex Java language features.

          I think this var/val discussion is really just about local variable types. No one sane wants to omit explicit types in public API (I believe)

Leave a Reply