I'm the author of a certain open-source library. One of the public interfaces has methods which use raw types like Collection
, for instance:
public StringBuilder append(..., Collection value);
I get Collection is a raw type. References to generic type Collection<E> should be parameterized
warnings.
I'm thinking about fixing these warnings. Implementations actually don't care about the types of the elements in collections. So I was thinking about replacing Collection<?>
.
However, these methods are parts of public interface of my library. Client code may call these methods or provide own implementations of these public interfaces thus implementing these methods. I am afraid that changing Collection
to Collection<?>
will break client code. So here is my question.
If I change Collection
-> Collection<?>
in my public interfaces, may this lead to:
The warning shows that raw types bypass generic type checks, deferring the catch of unsafe code to runtime. Therefore, you should avoid using raw types. The Type Erasure section has more information on how the Java compiler uses raw types.
The question mark (?) is known as the wildcard in generic programming. It represents an unknown type. The wildcard can be used in a variety of situations such as the type of a parameter, field, or local variable; sometimes as a return type.
It is not safe at runtime to make this replacement.
I should perhaps say more precisely that this change is safe by itself; but that subsequent changes that it encourages could lead to failures.
The difference between a Collection
and a Collection<?>
is that you can add anything to the former, whereas you cannot add anything except literal null to the latter.
So, somebody currently overriding your method might do something like:
@Override
public StringBuilder append(Collection value) {
value.add(Integer.valueOf(1));
return new StringBuilder();
}
(I don't know what the method is meant to be for; this is a pathological example. It certainly looks like something they shouldn't do, but that's not the same as them not doing so).
Now, let's say this method is called like so:
ArrayList c = new ArrayList();
thing.append(c);
c.get(0).toString();
(Again, I don't know how it is used for real. Bear with me)
If you changed the method signature to append
Collection<?>
in the superclass, perhaps surprisingly (*), you would not need to update the subclass to be generic too: the append
method above would continue to compile.
Seeing the new generic type of the parameter in the base class, you could then think that you could now make this calling code non-raw:
ArrayList<Double> c = new ArrayList<>();
thing.append(c);
c.get(0).toString();
Now, the gotcha here is how the last line is evaluated: there is an implicit cast in there. It would actually be evaluated something like:
Double d = (Double) c.get(0);
d.toString();
This is despite the fact you can invoke toString()
on an Object
: there is still a checkcast
inserted by the compiler, to the erasure of the list element type. This would fail at runtime, because the last item in the list is an Integer, not a Double
.
And the key point is that no cast is inserted for the raw-typed version. That would be evaluated like:
Object d = (Object) c.get(0);
d.toString();
This would not fail at runtime, because anything can be cast to object (in fact, there would be no cast at all; I am merely inserting it for symmetry).
This is not to say that such calling code could not exist before making the parameter Collection<?>
: it certainly could, and it would already fail at runtime. But the point I am trying to highlight is that making this method parameter generic could give the mistaken impression that it is safe to convert existing raw calling code to use generics, and doing so would cause it to fail.
So... Unless you can guarantee that there is no such insertion in subclasses, or you have explicitly documented that the collection should not be modified in third method, this change would not be safe.
(*) This arises as a consequence of the definition of override-equivalence, in JLS Sec 8.4.2, where erasure is explicitly considered.
You are not going to have any problems in runtime because generic types are erased from binaries -- see: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
You are also not going to have any problems on compile time for Collection<?>
and Collection
are equivalent. -- see: https://docs.oracle.com/javase/tutorial/extra/generics/legacy.html
Collection
is a collection of any type (ie it can contains any type of element: Integer, String, Object...)Collection<?>
is a collection of some specific type.The client code will not have any compilation error or runtime errors. since when you pass a collection to Collection<?>
it is treated as Collection<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