简体   繁体   English

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

[英]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. 假设我有一个通用接口Source<T> ,它是T对象的纯生产者。 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> 因此如果你有一个Source<? extends Foo>那么无论你用Source<Foo>做什么都可以做到, 这是一个合理的期望 Source<? extends Foo> Source<? extends Foo> . 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. 现在我需要在Source的主体中强制执行此限制,以便有人不会以违反该合同的方式意外使用T

An example from the JDK JDK的一个例子

As @Miserable.Variable points out, ArrayList<Integer> and ArrayList<? extends Integer> 正如@ Miserable.Variable所指出的, ArrayList<Integer>ArrayList<? extends Integer> ArrayList<? extends Integer> are not equivalent. ArrayList<? extends Integer> 等价。 That's because ArrayList is not covariant as a generic type. 那是因为ArrayList不是协变的泛型类型。 Or in other words, ArrayList<T> is not a pure producer of T ; 或者换句话说, ArrayList<T>不是一个纯生产者T ; specifically, the ArrayList method add(T) consumes a T . 具体来说, ArrayList方法add(T) 消耗 T

But there are generic types that are pure producers, like Iterator or Iterable . 但是有一些泛型类型是纯粹的生成Iterator ,比如IteratorIterable Whatever you can do with an Iterator<Integer> you can also do with an Iterator<? extends Integer> 无论你用Iterator<Integer>做什么,你也可以用Iterator<? extends Integer> Iterator<? extends Integer> . Iterator<? extends Integer> There is no method like ArrayList.add(T) in Iterator<T> . Iterator<T>没有类似ArrayList.add(T)方法。

I just want to make sure that my interface Source<T> is like Iterator<T> rather than like ArrayList<T> . 我只是想确保我的接口Source<T>Iterator<T>类似,而不是像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. 如果将来有人在我的界面中add(T)了一个T cosuming方法(比如add(T) ),我希望他们得到一个明确的错误。

A more complex example 一个更复杂的例子

Simply banning parameters of type T from appearing in the interface is not a full solution. 简单地禁止T型参数出现在界面中并不是一个完整的解决方案。 One should also note that T might be used as argument to other generic types. 还应该注意, T可以用作其他泛型类型的参数。 For example, the following method should not be allowed in Source<T> : 例如, 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; 因为偷偷摸摸的子类可能会尝试从列表中读取,它被认为是T cosumer; you cannot call this method on a Source<? extends Foo> 你不能在Source<? extends Foo>上调用这个方法Source<? extends Foo> Source<? extends Foo> . 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> .) (还有另一条规则说Source<T>方法不能返回List<T> ,但可以返回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? 是否有单元测试技巧,静态分析器,编译器/ IDE插件,注释处理器(例如在T上使用@Covariant注释),或任何其他可以确保这一点的技术或工具?

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> 因此,如果你有一个Source<? extends Foo>那么无论你用Source<Foo>做什么都可以做到,这是一个合理的期望Source<? extends Foo> 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> 你链接到整个pdf并进入顶级页面,因此不清楚你是如何确定这是合理的,但一般来说你不能随意用Foo<? extends T>替换Foo<T> Foo<? extends T> Foo<? extends T> . 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 例如,如果你有一个ArrayList<Integer> a你可以调用a.Add(Interger.valueOf(5))但是如果aArrayList<? extends Integer> a你就不能这样做ArrayList<? extends Integer> a ArrayList<? extends Integer> a . ArrayList<? extends Integer> a

It is also unclear what are Consumer<T> and sendTo . 还不清楚什么是Consumer<T>sendTo Is the latter a method in Source<T> >? 后者是Source<T>的方法吗?

Without these clarifications, I am afraid he question is ambiguous. 如果没有这些澄清,我担心他的问题是模棱两可的。

Well, if you don't want to have any put() method in your interface, all you have to do is not to write any and leave a comment to that effect somewhere in the code; 好吧,如果你不想在你的界面中有任何put()方法,那么你所要做的就是不写任何东西,并在代码的某处留下评论; preferably with a lot of asterixes around it. 最好是周围有很多星号。

Honestly, I don't understand at all the usefulness of having a feature that would give a language the possibility of blocking some contravariant methods to a class or an interface. 老实说,我完全不了解有一个功能可以让语言阻止某些逆变方法进入类或接口的功能。 This is the responsability of the programer, not the compilator. 这是程序员的责任,而不是编译器。

It's the same thing as if you would ask for a feature that would give you the possibility of blocking the addition of any function that would accept an Integer as one of its parameter or as its return value. 这是一个相同的事情,就好像你会要求一个功能,可以阻止添加任何接受一个I​​nteger作为其参数之一或作为其返回值的函数。 Having such a feature in a language would be totally useless in my opinion. 在我看来,在语言中使用这样的功能是完全没用的。

Maybe not the answer you are looking for, but I guess you could enforce it in your interface declaration. 也许不是你正在寻找的答案,但我想你可以在你的界面声明中强制执行它。

Enfoce Contravariant Use Enfoce逆变使用

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

}

Then you could use it like this: 然后你可以像这样使用它:

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

Enforce Covariant Use 强制协变使用

The contrary, of course, is also possible: 相反,当然也是可能的:

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

}

And you could use it like this: 你可以像这样使用它:

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

In both cases I have enforced that the underlying structure is either contravariant or covariant. 在这两种情况下,我都强制要求底层结构是逆变或协变。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM