什么是通用 < 的真实例子?超级T>?

What is a real life example of generic <? super T>?

我了解 <? super T> 代表 T 的任何超 class(任何级别的 T 的父级 class)。但我真的很难想象这个通用绑定通配符的任何真实示例。

我明白了<? super T>的意思,也看到了这个方法:

public class Collections {
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
  }
}

我正在寻找一个 现实生活用例 可以使用此构造的示例,而不是为了解释它是什么。

假设你有一个方法:

passToConsumer(Consumer<? super SubType> consumer)

然后你用任何可以消耗 SubType:

Consumer 调用这个方法
passToConsumer(Consumer<SuperType> superTypeConsumer)
passToConsumer(Consumer<SubType> subTypeConsumer)
passToConsumer(Consumer<Object> rootConsumer)

例如:

class Animal{}

class Dog extends Animal{

    void putInto(List<? super Dog> list) {
        list.add(this);
    }
}

所以我可以将 Dog 放入 List<Animal>List<Dog>:

List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();

Dog dog = new Dog();
dog.putInto(dogs);  // OK
dog.putInto(animals);   // OK

如果将 putInto(List<? super Dog> list) 方法更改为 putInto(List<Animal> list):

Dog dog = new Dog();

List<Dog> dogs = new ArrayList<>();
dog.putInto(dogs);  // compile error, List<Dog> is not sub type of List<Animal>

putInto(List<Dog> list):

Dog dog = new Dog();

List<Animal> animals = new ArrayList<>();
dog.putInto(animals); // compile error, List<Animal> is not sub type of List<Dog>

我写了一个网络广播,所以我有了 class MetaInformationObject,这是 PLS 和 M3U 播放列表的超级class。我有一个选择对话,所以我有:

public class SelectMultipleStreamDialog <T extends MetaInformationObject>
public class M3UInfo extends MetaInformationObject
public class PLSInfo extends MetaInformationObject

这个 class 有一个方法 public T getSelectedStream()
所以调用者收到一个具体类型(PLS 或 M3U)的 T,但需要在 superclass 上工作,所以有一个列表:List<T super MetaInformationObject>。添加结果的位置。
这就是通用对话可以处理具体实现的方式,其余代码可以在 superclass.
上运行 希望这能让它更清楚一点。

假设您有这个 class 层次结构: Cat 继承自 Mammal,而 Mammal 又继承自 Animal。

List<Animal> animals = new ArrayList<>();
List<Mammal> mammals = new ArrayList<>();
List<Cat> cats = ...

这些调用有效:

Collections.copy(animals, mammals); // all mammals are animals
Collections.copy(mammals, cats);    // all cats are mammals
Collections.copy(animals, cats);    // all cats are animals
Collections.copy(cats, cats);       // all cats are cats 

但是这些调用无效

Collections.copy(mammals, animals); // not all animals are mammals
Collections.copy(cats, mammals);    // not all mammals are cats
Collections.copy(cats, animals);    // mot all animals are cats

因此方法签名只是确保您从更具体的(继承层次中较低的)class复制到更通用的class(继承层次中的较高),而不是反过来。

假设你有:

class T {}
class Decoder<T>
class Encoder<T>

byte[] encode(T object, Encoder<? super T> encoder);    // encode objects of type T
T decode(byte[] stream, Decoder<? extends T> decoder);  // decode a byte stream into a type T

然后:

class U extends T {}
Decoder<U> decoderOfU;
decode(stream, decoderOfU);     // you need something that can decode into T, I give you a decoder of U, you'll get U instances back

Encoder<Object> encoderOfObject;
encode(stream, encoderOfObject);// you need something that can encode T, I give you something that can encode all the way to java.lang.Object

我能想到的最简单的例子是:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

取自同一个 Collections。这样 Dog 可以实现 Comparable<Animal>,如果 Animal 已经实现了,Dog 就不需要做任何事情。

编辑一个真实的例子:

经过一些电子邮件乒乓球后,我被允许展示我工作场所的一个真实例子(耶!)。

我们有一个接口叫做Sink(不管它做什么),想法是积累的东西。声明非常简单(简化):

interface Sink<T> {
    void accumulate(T t);
}

显然有一个辅助方法接受 List 并将它的元素排放到 Sink(它有点复杂,但为了简单起见):

public static <T> void drainToSink(List<T> collection, Sink<T> sink) {
    collection.forEach(sink::accumulate);
}

这很简单吧?嗯...

我可以有一个 List<String>,但我想把它排到 Sink<Object> - 这对我们来说是很常见的事情;但这会失败:

Sink<Object> sink = null;
List<String> strings = List.of("abc");
drainToSink(strings, sink);

为此,我们需要将声明更改为:

public static <T> void drainToSink(List<T> collection, Sink<? super T> sink) {
    ....
}

为此,我想到了一些现实生活中的例子。我想提出的第一个想法是将真实世界的对象用于 'improvised' 功能。假设您有一把套筒扳手:

public class SocketWrench <T extends Wrench>

套筒扳手的明显用途因此被用作 Wrench。但是,如果您认为扳手可以在紧要关头用来敲钉子,那么您的继承层次结构可能如下所示:

public class SocketWrench <T extends Wrench>
public class Wrench extends Hammer

在这种情况下,您可以调用 socketWrench.pound(Nail nail = new FinishingNail()),尽管这将被视为 SocketWrench 的非典型用法。

一直以来,如果 SocketWrench 被用作 SocketWrench 而不仅仅是 Wrench,则它可以调用 applyTorque(100).withRotation("clockwise").withSocketSize(14) 之类的方法, 而不是 Hammer.

例如,查看 Collections.addAll 方法实现:

public static <T> boolean addAll(Collection<? super T> c, T... elements) {
    boolean result = false;
    for (T element : elements)
        result |= c.add(element);
    return result;
}

在这里,元素可以插入到元素类型是元素类型 T 的超类型的任何集合中。

没有下界通配符:

public static <T> boolean addAll(Collection<T> c, T... elements) { ... }

以下内容无效:

List<Number> nums = new ArrayList<>();
Collections.<Integer>addAll(nums , 1, 2, 3);

因为术语 Collection<T>Collection<? super T> 更严格。


另一个例子:

Predicate<T> Java 中的接口,在以下方法中使用 <? super T> 通配符:

default Predicate<T> and(Predicate<? super T> other);

default Predicate<T>  or(Predicate<? super T> other);

<? super T> 允许链接范围更广的不同谓词,例如:

Predicate<String> p1 = s -> s.equals("P");
Predicate<Object> p2 = o -> o.equals("P");

p1.and(p2).test("P"); // which wouldn't be possible with a Predicate<T> as a parameter

考虑这个简单的例子:

List<Number> nums = Arrays.asList(3, 1.2, 4L);
Comparator<Object> numbersByDouble = Comparator.comparing(Object::toString);
nums.sort(numbersByDouble);

希望这是一个有点令人信服的案例:您可以想象想要对数字进行排序以用于显示目的(toString 是一个合理的排序),但 Number 本身不是 Comparable。

之所以编译,是因为 integers::sort 采用了 Comparator<? super E>。如果它只需要一个 Comparator<E>(在这种情况下 ENumber),那么代码将无法编译,因为 Comparator<Object> 不是 [=18= 的子类型](由于你的问题说明你已经明白了,所以我就不赘述了)。

集合就是一个很好的例子。

1 中所述,List<? super T> 允许您创建 List 来保存类型元素,派生程度低于 T,因此它可以保存元素继承自 T,属于 T 的类型,并且 T 继承自

另一方面,List<? extends T> 允许您定义一个 List,它只能包含继承自 T 的元素(在某些情况下甚至不是 T 类型的元素) ).

这是一个很好的例子:

public class Collections {
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
  }
}

此处您要将较少派生类型的 List 投影到较少派生类型的 List。 这里 List<? super T> 向我们保证来自 src 的所有元素在新集合中都是有效的。

1 :