HomeAboutArchivesTools & ResourcesSupport me
Hi! 👋 I'm Szymon

I help you become a better software developer.

  • 1.15kFollowers
  • 2.36kSubscribers
  • 110Followers
  • 30.2kReputation

Latest posts from the Learning Java

GraalVM and heap size of the native image - how to set it?
Installing GraalVM EE 1.0.0-RC14 with SDKMAN!
Java 8 type inference in generic methods chain call - what mi...
Divide a list to lists of n size in Java 8
More

Popular categories

  • Groovy Cookbook28
  • Jenkins Pipeline Cookbook9
  • Programmer's Bookshelf6
  • Micronaut Cookbook4
  • Ratpack Cookbook4
  • Learning Java4
  • jq cookbook3
  • How to3
  • Blog Reports2

Don't miss anything - join my newsletter

Additional materials and updates for subscribers. No spam guaranteed.
Unsubscribe at any time.

Did you like this article?

Spread the !
  • ☕️
  1. e.printstacktrace.blog
  2. Learning Java

Java 8 type inference in generic methods chain call - what might go wrong?

  • October 28, 2018
  • 4 min. read
  • 0 Comments

Yesterday I have found this interesting question on Stack Overflow asked by Opal. He faced some unexpected compilation errors when dealing with Java generics and Vavr library. It turned out the root cause of the issue there was not the library, but Java compiler itself. This was pretty interesting use case and it motivated me to investigate it even further. This blog post reveals untold truth about Java generics type inference. Are you ready? :)

An example

Let’s define a simple Some<T> generic class:

import java.util.function.Consumer;
import java.util.function.Supplier;

public final class Some<T> {

    private final T value;

    private Some(final T t) {
        this.value = t;
    }

    static <T> Some<T> of(final Supplier<T> supplier) {
        return new Some<>(supplier.get());
    }

    public Some<T> peek(final Consumer<T> consumer) {
        consumer.accept(value);
        return this;
    }

    public T get() {
        return value;
    }
}

Some<T> represents some value provided by a supplier function. There is not much we can do we this object - its class provides only two additional methods, peek(Consumer<T> consumer) and get(). But that’s enough for this demo.

There is one useful thing we would like to take advantage of - peek() method returns Some<T> which in this case is the reference to the caller object. This is very handy and it allows us to create a chain of methods. Let’s try it out and create Some<List<? extends CharSequence>> object:

Listing 1. Example 1
final class SomeExample {

    public static void main(String[] args) {
        Some<List<? extends CharSequence>> some =
                Some.of(() -> Arrays.asList("a", "b", "c"));

        System.out.println(some.get());
    }
}

So far so good - this simple example compiles and produces [a, b, c] in the console log when executed. Let’s modify the code a bit and use peek() method instead System.out.println(some.get()):

Listing 2. Example 2
final class SomeExample {

    public static void main(String[] args) {
        Some<List<? extends CharSequence>> some =
                Some.of(() -> Arrays.asList("a", "b", "c")).peek(System.out::println);
    }
}

And now something unexpected happens. Suddenly, compiler started complaining about incompatible types:

At some point it makes sense, because Java generics are invariant[1] - Java Language Specification in chapter 4.10. Subtyping says clearly:

Subtyping does not extend through parameterized types: T <: S does not imply that C<T> <: C<S>.

So why does the example without peek() method invocation worked?

Generalized Target-Type Inference

In the first example we have took an advantage of the feature introduced in Java 8 with JSR-335[2] - Generalized Target-Type Inference, proposed as JEP-101[3]. It added a whole new chapter to the language specification - Chapter 18. Type Inference[4]. It made Java compiler context aware when it comes to type inference, so it can deduct the expected type from the left side of the expression and subtype[5] if needed.

It explains why Java compiler is satisfied by reducing expression on the right side to type Some<List<? extends CharSequence>> while assigning value to a some variable:

It shows that even if:

  • Arrays.asList("a", "b", "c") returns List<String>,

  • Some.of(() → Arrays.asList("a", "b", "c")) returns Some<List<String>>,

then assigning it to a type like Some<List<? extends CharSequence>> changes the invocation context and in this context Some.of() returns type defined on the left side of the assignment.

Limitations

Java 8 type inference system has some limitations. One of them is type inference in chain methods call. JEP-101 mentions this problem, and our second example proves this limitation exists:

When we start chaining methods, compiler delays type inference until the expression on the right side gets evaluated. Last method in the chain receives Some<List<String>> from the first method and it passes it to variable assignment. This is why we see compiler error in this case.

Solutions

There are two solutions (or workarounds) to this limitation.

1) We can specify explicitly generic type:

final class SomeExample {

    public static void main(String[] args) {
        Some<List<? extends CharSequence>> some =
                Some.<List<? extends CharSequence>>of(() -> Arrays.asList("a", "b", "c")).peek(System.out::println);
    }
}

In this case we instruct compiler that we expect Some.of() to return this specific type and it gets passed to peek() method which returns previously specified type back.

2) We can break the chain and split assignment from the rest chain calls

final class SomeExample {

    public static void main(String[] args) {
        Some<List<? extends CharSequence>> some = Some.of(() -> Arrays.asList("a", "b", "c"));
        some.peek(System.out::println);
    }
}

1. https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.10
2. http://cr.openjdk.java.net/~dlsmith/jsr335-final/spec/G.html
3. https://openjdk.java.net/jeps/101
4. https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html
5. http://cr.openjdk.java.net/~dlsmith/jsr335-final/spec/G.html#18.2.3_Subtyping_Constraints_.5BNew.5D
  • java
  • java-8
  • java-generics
  • type-inference
  • jsr335
  • jep101

Micronaut Cookbook

Non-blocking and async Micronaut - quick start (part 3)

  • October 30, 2018
  • 4 min. read
  • 0 Comments

Welcome to the part 3 of "Non-blocking and async Micronaut" article. In the previous post we have created connection between two services using HTTP protocol and we have run some experiments with handling 10,000 requests. Today we are going to extend this example by setting some timeouts to see what happens.

Learning Java

Divide a list to lists of n size in Java 8

  • September 9, 2017
  • 4 min. read
  • 0 Comments

Every Java developer works with lists daily. There are many popular list (or collection) operati...

Groovy Cookbook

What is the most efficient way to iterate collection in Groovy? Let's play with JMH!

  • October 2, 2018
  • 4 min. read
  • 0 Comments

I guess you may heard about Groovy’s Collection.each(Closure cl) method - it was introduce...

Groovy Cookbook

How Groovy's equal operator differs from Java?

  • September 22, 2018
  • 4 min. read
  • 0 Comments

One of the first mistakes people do when starting their journey with Java programming language i...

Any thoughts or ideas?

Let's talk in the comment's section 💬

Want to put a code sample in the comment? Read the Syntax highlighting guide for more information.
Empty
Latest blog posts
  • Merging JSON files recursively in the command-line
  • Jenkins Declarative Pipeline with the dynamic agent - how to configure it?
  • Groovy Ecosystem Usage Report (2020)
  • How to convert JSON to CSV from the command-line?
  • 5 Common Jenkins Pipeline Mistakes
  • How to merge two maps in Groovy?
  • Building stackoverflow-cli with Java 11, Micronaut, Picocli, and GraalVM
  • How to catch curl response in Jenkins Pipeline?
  • Atomic Habits - book review
  • Parsing JSON in command-line with jq: basic filters and functions (part 1)
Trending videos
Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline

Jenkins Pipeline as a code is a new standard for defining continuous integration and delivery pipelines in Jenkins. The scrip...

Using GraalVM native-image with a Groovy script - from 2.1s to 0.013s startup time 🚀

GraalVM native-image compiler uses ahead-of-time compilation to produce a highly optimized standalone executable file that ca...

Useful links
  • Start here
  • About
  • Archives
  • Resources
  • Privacy Policy
  • Merchandise
  • My Kit
  • RSS
  • Support the blog
Popular categories
  • Groovy Cookbook28
  • Jenkins Pipeline Cookbook9
  • Programmer's Bookshelf6
  • Micronaut Cookbook4
  • Ratpack Cookbook4
  • Learning Java4
  • jq cookbook3
  • How to3
  • Blog Reports2
Popular tags
affiliate async benchmark blogging book career cicd continuous-integration curl devops docker git github graalvm gradle grails groovy haskell hexo java java-8 jenkins jenkins-pipeline jenkinsfile jmh jq json junit learning maven metaprogramming micronaut native-image non-blocking progress ratpack reactive-programming reading recursion review rxjava sdkman session split spock stackoverflow string tail-call tail-recursion unit-test
  • Designed by @wololock
  • Created with Hexo
  • Hosted on GitHub
  • Deployed with Circle CI
  • License CC BY-NC-SA 4.0