Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it backwards-compatible to replace raw type like Collection with wildcard like Collection<?>?

Tags:

java

generics

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:

  • compilation errors in the client code?
  • runtime errors in the already compiled existing client code?
like image 603
lexicore Avatar asked Jun 02 '18 14:06

lexicore


People also ask

Why is a raw type unsafe in Java?

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.

What is wildcard in Java generic?

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.


Video Answer


3 Answers

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.

like image 56
Andy Turner Avatar answered Oct 13 '22 00:10

Andy Turner


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

like image 45
Kalecser Avatar answered Oct 13 '22 01:10

Kalecser


  1. Collection is a collection of any type (ie it can contains any type of element: Integer, String, Object...)
  2. 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>

like image 43
Mạnh Quyết Nguyễn Avatar answered Oct 13 '22 01:10

Mạnh Quyết Nguyễn