7

假设我有Source<T>一个纯T对象生产者的通用接口。作为一个纯粹的生产者是接口契约的一部分。因此,一个合理的期望是,无论你用 aSource<Foo>做什么,如果你有 a 也应该可以做到Source<? extends Foo>

现在我需要在 的正文中强制执行此限制Source,以免有人意外T以与该合同相矛盾的方式使用。

来自 JDK 的示例

正如@Miserable.Variable 指出的那样ArrayList<Integer>并不ArrayList<? extends Integer>等同。那是因为它作为泛型类型不是协变的。或者换句话说,不是一个纯粹的生产者;具体来说,该方法消耗一个.ArrayListArrayList<T>TArrayListadd(T) T

但是有一些泛型类型是纯粹的生产者,比如Iteratoror Iterable。无论你可以用一个做什么,Iterator<Integer>你也可以用一个Iterator<? extends Integer>。没有类似ArrayList.add(T)in 的方法Iterator<T>

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

一个更复杂的例子

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

public void copyTo(List<T> destination);

因为偷偷摸摸的子类可能会尝试从列表中读取,所以它被认为是T-consumer;您不能在Source<? extends Foo>. 另一方面,应该允许这个:

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

(还有另一条规则说方法 inSource<T>不能返回 a List<T>,但可以返回 a List<? extends T>。)

现在,实际的接口可以任意复杂,有很多方法,规则本身也很复杂。很容易犯错。所以我想自动化这个检查。

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

4

3 回答 3

2

这不是一个答案,但太长了,无法放入评论中。

因此,合理的期望是Source<Foo>,如果您有 a ,则您可以用 a 做的任何事情也应该可以做Source<? extends Foo>

不,这不是一个合理的期望。您链接到整个 pdf 并转到顶级页面,因此不清楚您如何确定这是合理的,但通常您不能随意将 a 替换为Foo<T>a Foo<? extends T>。举个例子,如果你有一个ArrayList<Integer> a你可以打电话a.Add(Interger.valueOf(5))但你不能这样做如果aArrayList<? extends Integer> a

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

如果没有这些澄清,恐怕他的问题是模棱两可的。

于 2012-12-28T19:30:05.200 回答
2

好吧,如果您不想在您的界面中使用任何 put() 方法,您所要做的就是不要编写任何方法并在代码中的某处留下对此效果的注释;最好在它周围有很多星号。

老实说,我完全不明白拥有一个特性的用处,它可以让语言有可能阻止类或接口的一些逆变方法。这是程序员的责任,而不是编译器。

这就像您要求提供一种功能,该功能使您有可能阻止添加任何接受整数作为其参数之一或作为其返回值的函数。在我看来,在语言中拥有这样的功能是完全没用的。

于 2012-12-29T15:47:49.763 回答
0

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

强制逆变使用

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();

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

于 2012-12-29T16:47:01.300 回答