An unbound type can be useful when your method doesn’t really care about the actual type.
A primitive example would be this:
public void printStuff(Iterable<?> stuff) {
for (Object item : stuff) {
System.out.println(item);
}
}
Since PrintStream.println()
can handle all reference types (by calling toString()
), we don’t care what the actual content of that Iterable
is.
And the caller can pass in a List<Number>
or a Set<String>
or a Collection<? extends MySpecificObject<SomeType>>
.
Also note that not using generics (which is called using a raw type) at all has a quite different effect: it makes the compiler handle the entire object as if generics don’t exist at all. In other words: not just the type parameter of the class is ignored, but also all generic type parameters on methods.
Another important distinctions is that you can’t add any (non-null
) value to a Collection<?>
, but can add all objects to the raw type Collection
:
This won’t compile, because the type parameter of c
is an unknown type (= the wildcard ?
), so we can’t provide a value that is guaranteed to be assignable to that (except for null
, which is assignable to all reference types).
Collection<?> c = new ArrayList<String>();
c.add("foo"); // compilation error
If you leave the type parameter out (i.e. use a raw type), then you can add anything to the collection:
Collection c = new ArrayList<String>();
c.add("foo");
c.add(new Integer(300));
c.add(new Object());
Note that the compiler will warn you not to use a raw type, specifically for this reason: it removes any type checks related to generics.