如何确保只使用covariantly类型参数?

时间:2012-12-23 21:06:38

标签: java unit-testing generics static-analysis covariance

假设我有一个通用接口Source<T>,它是T个对象的纯生成器。作为纯粹的生产者是界面合同的一部分。因此it is a reasonable expectation无论您使用Source<Foo>做什么,如果您拥有Source<? extends Foo>,也应该可以做到。

现在我需要在Source的正文中强制执行此限制,以便有人不会以违反该合同的方式意外使用T

JDK的一个例子

正如@ Miserable.Variable指出的那样,ArrayList<Integer>ArrayList<? extends Integer> 不等于。这是因为ArrayList不是协变的泛型类型。或者换句话说,ArrayList<T>不是T的纯生产者;具体而言,ArrayList方法add(T) 消耗 a T

但是有一些纯粹的生产者,如IteratorIterable。无论您使用Iterator<Integer>做什么,您都可以使用Iterator<? extends Integer>ArrayList.add(T)中没有类似Iterator<T>的方法。

我只是想确保我的界面Source<T>Iterator<T>相似,而不是像ArrayList<T>。如果将来有人在我的界面中添加T消耗方法(如add(T)),我希望他们得到明确的错误。

更复杂的例子

简单地禁止类型T的参数出现在界面中并不是一个完整的解决方案。还应该注意T可以用作其他泛型类型的参数。例如,Source<T>

中不允许使用以下方法
public void copyTo(List<T> destination);

因为偷偷摸摸的子类可能会尝试从列表中读取,它被认为是T - 消费者;你无法在Source<? extends Foo>上调用此方法。另一方面,应该允许这个:

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

(另一条规则是Source<T>中的方法无法返回List<T>,但可以返回List<? extends T>。)

现在,实际的界面可能是任意复杂的,有很多方法,而且规则本身也很复杂。犯错很容易。所以我想自动完成这项检查。

是否有单元测试技巧,静态分析器,编译器/ IDE插件,注释处理器(例如@Covariant上有T注释),或任何其他可以确保这一点的技术或工具对我来说?

3 个答案:

答案 0 :(得分:2)

这不是一个答案,但是太长,不适合评论。

  

因此,如果您有Source<Foo>

,那么无论您使用Source<? extends Foo>做什么都可以做到,这是合理的期望。

不,这不是一个合理的期望。您链接到整个pdf并转到top level page,因此不清楚您如何确定这是合理的,但一般情况下,您不能随意用Foo<T>替换Foo<? extends T>。例如,如果您有ArrayList<Integer> a,则可以致电a.Add(Interger.valueOf(5))但如果aArrayList<? extends Integer> a,则无法执行此操作。

还不清楚Consumer<T>sendTo是什么。后者是Source<T>&gt;中的方法吗?

如果没有这些澄清,我担心他的问题是模棱两可的。

答案 1 :(得分:2)

好吧,如果你不想在你的界面中有任何put()方法,那么你所要做的就是不要写任何东西,并在代码的某处留下评论;最好是周围有很多星号。

老实说,我完全不了解有一个功能可以让语言阻止某些逆变方法进入类或接口的功能。这是程序员的责任,而不是编译器。

同样的事情就好像你会要求一个能够阻止添加任何接受整数作为其参数或其返回值的函数的功能。在我看来,用一种语言提供这样的功能是完全没用的。

答案 2 :(得分:0)

也许不是您正在寻找的答案,但我想您可以在您的界面声明中强制执行。

Enfoce Contravariant Use

public interface Consumer<T, S extends T> {
   public void consume(S item);
}

public static class ConsumerImpl<T, S extends T> implements Consumer<T, S> {

   private List<? super S> items = new ArrayList<T>();

   public void consume(S item) {
      this.items.add(item);
   }

}

然后你可以像这样使用它:

Consumer<Object, String> consumer = new ConsumerImpl<Object, String>();
consumer.consume("Whatever");
consumer.consume("Another");

强制使用协变

相反,当然也是可能的:

public interface Producer<T, S extends T> {
   public T produce();
}

public static class ProducerImpl<T, S extends T> implements Producer<T,S> {

   private Deque<? extends T> items = new ArrayDeque<S>();

   public ProducerImpl(Deque<S> items) {
      this.items = items;
   }

   public T produce() {
      return items.pop();
   }

}

你可以像这样使用它:

Deque<Integer> myInts = new ArrayDeque<Integer>();
myInts.push(1);
myInts.push(2);

Producer<Number, Integer> producer = new ProducerImpl<Number, Integer>(myInts);
Number n1 = producer.produce();
Number n2 = producer.produce();

在这两种情况下,我都强制要求底层结构是逆变或协变。