Why Bar.go
is OK
with argument f2
but not with argument f1
?
public class HelloWorld {
public static void main(String[] args) {
Foo<Foo<?>> f1 = new Foo<Foo<?>>();
Foo<Foo<String>> f2 = new Foo<Foo<String>>();
Bar.go(f1); // not OK
Bar.go(f2); // OK
}
public static void p(Object o) {
System.out.println(o);
}
}
class Foo<E> {
}
class Bar {
public static <T> void go(Foo<Foo<T>> f) {
}
}
Shouldn't the compiler automatically infer type T
as capture of ?
in both cases?
The question mark (?) is known as the wildcard in generic programming. It represents an unknown type.
In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific).
We can use the Java Wildcard as a local variable, parameter, field or as a return type. But, when the generic class is instantiated or when a generic method is called, we can't use wildcards. The wildcard is useful to remove the incompatibility between different instantiations of a generic type.
both bounded and unbounded wildcards provide a lot of flexibility on API design especially because Generics is not covariant and List<String> can not be used in place of List<Object>. Bounded wildcards allow you to write methods that can operate on Collection of Type as well as Collection of Type subclasses.
Great question!
(In the following comments, w.r.t. a class generic in E
like Foo< E >
, define "covariant method" as a method that returns an E
without having any parameters using E
, and a "contravariant method" as the opposite: one which takes a formal parameter of type E
but doesn't return a type involving E
. [The real definition of these terms is more complicated, but never mind that for now.])
It seems that the compiler is trying to bind T
to Object
in the case of f1
, because if you do
class Bar0 {
public static < T > void go( Foo< Foo< ? extends T > > f ) {
// can pass a Foo< T > to a contravariant method of f;
// can use any result r of any covariant method of f,
// but can't pass T to any contravariant method of r
}
}
then the go(f1)
works, but now go(f2)
doesn't, because even though Foo< String > <: Foo< ? extends String >
, that does not imply that Foo< Foo< String > > <: Foo< Foo< ? extends String > >
.
Here are a few modifications that compile for both f1
and f2
:
class Bar1 {
public static < T > void go( Foo< ? super Foo< T > > f ) {
// can't properly type the results of any covariant method of f,
// but we can pass a Foo< T > to any contravariant method of f
}
}
class Bar2 {
public static < T > void go( Foo< ? extends Foo< ? extends T > > f ) {
// can't pass a Foo< T > to a contravariant method of f;
// can use result r of any covariant method of f;
// can't pass a T to a contravariant method of r;
// can use result of covariant method of r
}
}
Foo<Foo<?>> f1 = new Foo<Foo<?>>();
This implies that the type is unknown and objects of any type can be added to Foo<Foo<?>>
that are heterogeneous and compiler cannot guarantee that all object in Foo<Foo<?>>
are of same type. Hence it cannot be passed to Bar.go
that takes a bounded type as parameter.
You can instead declare that as Foo<Foo<Object>> f1 = new Foo<Foo<Object>>();
to pass it to Bar.go
where you explicitly mention everything is of type Object
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With