Search code examples
javagenericstype-inferencetype-safetyunbounded-wildcard

Why simple “capture of ?” does not compile even type-safety could be compile-time inferred?


I have a class with strict, simple generic type:

public class GenericTools<T> {

    private final Supplier<T> supplier;
    private final Consumer<T> consumer;

    public GenericTools(Supplier<T> supplier, Consumer<T> consumer) {
        this.supplier = supplier;
        this.consumer = consumer;
    }

    public Supplier<T> getSupplier() {
        return supplier;
    }

    public Consumer<T> getConsumer() {
        return consumer;
    }
}

What is the exact reason for the fact that "capture of ?" cannot be used here and file does not compile?

GenericTools<?> tools = new GenericTools<>(Math::random, System.out::println);
tools.getConsumer().accept(tools.getSupplier().get());

Error:(27, 59) java: incompatible types: java.lang.Object cannot be converted to capture#1 of ?

With explicit <Double> it compiles with no problems:

GenericTools<Double> tools = new GenericTools<>(Math::random, System.out::println);
tools.getConsumer().accept(tools.getSupplier().get());

I have used Java 1.8 to compile.

Please note that this is completely not a duplicate of Java generics “capture of ?”, where poster have no idea what we need to pass as "?"-typed argument when code requires it. In my case I am quite aware of capture mechanism, but stil type looking like supposed to work cannot be used. Most importantly, here I am asking about the exact reason (specification reference or something), not about what I should pass.


Solution

  • It's because the type ? is invariant, and Object is not a subtype of all types within the bounds of ?.

    I believe the Java 8 type inference is capable of inferring that T is Double on the RHS, but since you explicitly assign to a GenericTools<?> on the LHS, the capture is of an unbounded type variable, which unifies with the unbounded variable T which also has no bounds.

    Without any bounds, the T in the signatures of Supplier::get and Consumer::accept are not guaranteed to be the same type -- remember, the type variable is invariant, as no co- or contra-variant bound is expressed. The erasure of the T on the Supplier side is just Object, and the compiler cannot insert a runtime check that the runtime type is actually ? (because ? is not reifiable!). Therefore: the type Object cannot be implicitly converted to ? and compilation fails.