Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make sure a type parameter is used only covariantly?

Suppose I have a generic interface Source<T> which is a pure producer of T objects. Being a pure producer is part of the contract of the interface. So it is a reasonable expectation that whatever you can do with a Source<Foo>, should be also possible to do if you have a Source<? extends Foo>.

Now I need to enforce this restriction in the body of Source, so that someone does not accidentally use T in a way that contradicts that contract.

An example from the JDK

As @Miserable.Variable points out, ArrayList<Integer> and ArrayList<? extends Integer> are not equivalent. That's because ArrayList is not covariant as a generic type. Or in other words, ArrayList<T> is not a pure producer of T; specifically, the ArrayList method add(T) consumes a T.

But there are generic types that are pure producers, like Iterator or Iterable. Whatever you can do with an Iterator<Integer> you can also do with an Iterator<? extends Integer>. There is no method like ArrayList.add(T) in Iterator<T>.

I just want to make sure that my interface Source<T> is like Iterator<T> rather than like ArrayList<T>. If someone in the future adds a T-consuming method (like add(T)) to my interface, I want them to get an explicit error.

A more complex example

Simply banning parameters of type T from appearing in the interface is not a full solution. One should also note that T might be used as argument to other generic types. For example, the following method should not be allowed in Source<T>:

public void copyTo(List<T> destination);

because an sneaky subclass may try to read from the list, it is considered a T-consumer; you cannot call this method on a Source<? extends Foo>. On the other hand, this one should be allowed:

public void copyTo(List<? super T> destination);

(There is also another rule that says methods in Source<T> cannot return a List<T>, but can return a List<? extends T>.)

Now, the actual interface can be arbitrarily complex with lots of methods, and the rules are pretty complex themselves. It is very easy to make a mistake. So I want to automate this check.

Is there a unit-testing trick, static analyzer, compiler/IDE plugin, annotation processor (for example with an @Covariant annotation on T), or any other technique or tool that can ensure this for me?

like image 541
Saintali Avatar asked Dec 23 '12 21:12

Saintali


1 Answers

This is not an answer, but too long to fit in a comment.

So it is a reasonable expectation that whatever you can do with a Source<Foo>, should be also possible to do if you have a Source<? extends Foo>

No, it is not a reasonable expectation. You linked to an entire pdf and it goes to a top level page so it is unclear how you determined this is reasonable, but in general you cannot arbitrarily replace a Foo<T> with a Foo<? extends T>. Foe example, if you have an ArrayList<Integer> a you can call a.Add(Interger.valueOf(5)) but you cannot do that if a is ArrayList<? extends Integer> a.

It is also unclear what are Consumer<T> and sendTo. Is the latter a method in Source<T>>?

Without these clarifications, I am afraid he question is ambiguous.

like image 74
Miserable Variable Avatar answered Oct 19 '22 07:10

Miserable Variable