Generic type inference limits in Java

2020-06-17 05:34发布

问题:

I'm facing the following problem in my project with Java generics type inference. This is a code sample that's similar to my original one:

public class BuildableObject<R, S> {
  public static class OneParameter<R> { }
  public static class TwoParameters<R, S> { }
  interface TwoParamInterface<R, S> { }
  public static class Implementer<T> implements TwoParamInterface<T, T> {}

  private final OneParameter<R> first;
  private final OneParameter<S> second;
  private final TwoParameters<R, S> third;
  private final TwoParamInterface<R, S> fourth;

  private BuildableObject(OneParameter<R> first, OneParameter<S> second, TwoParameters<R, S> third, TwoParamInterface<R, S> fourth) {
    this.first = first;
    this.second = second;
    this.third = third;
    this.fourth = fourth;
  }

  public static class Builder<R, S> {
    private OneParameter<R> first = null;
    private OneParameter<S> second = null;
    private TwoParameters<R, S> third = null;
    private TwoParamInterface<R, S> fourth = null;

    public Builder() {}

    public Builder<R, S> first(OneParameter<R> first) {
      this.first = first; return this;
    }

    public Builder<R, S> second(OneParameter<S> second) {
      this.second = second; return this;
    }

    public Builder<R, S> third(TwoParameters<R, S> third) {
      this.third = third; return this;
    }

    public Builder<R, S> fourth(TwoParamInterface<R, S> fourth) {
      this.fourth = fourth; return this;
    }

    public BuildableObject<R, S> build() {
      return new BuildableObject<>(first, second, third, fourth);
    }
  }

  public static void main(String... args) {
    new Builder<>()
        .first(new OneParameter<>())
        .second(new OneParameter<>())
        .third(new TwoParameters<>())
        .fourth(new Implementer<String>())
        .build();
  }
}

This code breaks at new Implementer<String>, but works if I use new Builder<String, String> instead of new Builder<>.

Why can't Java infer that the type of the Builder is Builder<String, String> if the types of R and S are specified in new Implementer<String>?

What are the limits of Java generic types inference? Does it only resolve types provided in constructors or static methods? I haven't found any documentation on this.

Does this mean in any way that this class might not be type safe if we can't use type inference?

回答1:

It's documented in detail in https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html. But the problem is that it's documented in detail: there is a lot of jargon you are unlikely to be familiar with unless you read papers on this subject.

For this case you just need to understand that for type inference it doesn't matter what methods you call after new Builder<>(); only parameters to the constructor itself are used (and a target type e.g. in Builder<String, String> b = new Builder<>();, but in this case you don't have one).

Does it only resolve types provided in constructors or static methods?

No.

Does this mean in any way that this class might not be type safe if we can't use type inference?

They are completely unrelated.



回答2:

Generic type inference can fail when the code is too complicated, but usually you can explicitly specify the types so that inference isn't necessary. Chained method calls can cause this sometimes. It just means you lose the convenience of not having to specify the type yourself.

Generic type checking is a separate concept. Generally the checker doesn't see a difference between an inferred type (<> inferred as <String, String>) and an explicit type (<String, String> written in the code), either way it's going to check that only strings are used with that object. As long as the compiler doesn't complain, your class should be type-safe.