Reputation: 5548
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.
Upvotes: 4
Views: 562
Reputation: 60957
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.
Upvotes: 2
Reputation: 13407
As written in the Generics tutorial:
Collection<?> c = new ArrayList<String>(); c.add(new Object()); // Compile time error
Since we don't know what the element type of
c
stands for, we cannot add objects to it. Theadd()
method takes arguments of typeE
, the element type of the collection. When the actual type parameter is?
, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don't know what type that is, we cannot pass anything in. The sole exception isnull
, which is a member of every type.On the other hand, given a
List<?>
, we can callget()
and make use of the result. The result type is an unknown type, but we always know that it is an object. It is therefore safe to assign the result ofget()
to a variable of typeObject
or pass it as a parameter where the typeObject
is expected.
This is equivalent to
GenericTools<?> tools = ...
tools.getConsumer().accept(new Object()); //or tools.getSupplier().get() which returns Object
Once you declare your generic type this is what the compiler will know for the rest of the calls. getConsumer().accept()
will have to accept ?
(but it can't) and getSupplier().get()
will have to return Object
.
When declaring GenericTools<Double>
you retain type information and both getConsumer().accept()
and getSupplier().get()
know that they are working with Double
.
Upvotes: 1