【问题标题】:What is a real life example of generic <? super T>?什么是泛型 < 的真实例子?超级T>?
【发布时间】:2018-09-05 13:06:27
【问题描述】:

我了解&lt;? super T&gt; 代表T 的任何超类(任何级别的T 的父类)。但我真的很难想象这个通用绑定通配符的任何现实生活示例。

我明白&lt;? super T&gt;是什么意思,我也见过这个方法:

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

我正在寻找一个现实生活用例的例子,在这个例子中可以使用这种结构,而不是解释它是什么。

【问题讨论】:

  • 这不是重复的,一个非常有效的问题
  • 我也不认为是重复的,他是在问具体情况,而不是背后的原理
  • 这是我即将在这个关闭时写的答案的开始:我在某种程度上同意接近投票者:答案可以推导出来,来自stackoverflow.com/questions/2723397/… 的一些勤奋。但是,这个问题(以及这里的答案)侧重于技术原理。一个简单的现实示例说明使用? super T 的意义可能会有所帮助。
  • 不要认为这应该作为重复关闭,因为作者要求的是真实世界的 OOP 模型,而不是深入解释 Java 中的继承如何工作。
  • 这里的例子难道不是一个真实的用例吗?

标签: java generics inheritance super


【解决方案1】:

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

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

取自同一个Collections。这样Dog 可以实现Comparable&lt;Animal&gt;,如果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&lt;String&gt;,但我想把它排到Sink&lt;Object&gt;——这对我们来说是相当常见的事情;但这会失败:

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) {
    ....
}

【讨论】:

  • Kotlin tutorial 使用 Source 作为示例...这基本上是您的 Sink 示例的对偶。
  • 您当然可以通过制作列表以相反的方式定义它吗?扩展 T?
  • @WeckarE。如果方法本身在列表中,则不会。
  • @WeckarE。我可以,但是这会稍微改变语义,现在我无法向list 添加任何内容,它仅成为生产者;正如所说,这是一个简化的例子......
【解决方案2】:

假设你有这个类层次结构: 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

因此,方法签名只是确保您从更具体的(继承层次结构中较低的)类复制到更通用的类(继承层次结构中的上层),而不是相反。

【讨论】:

  • super 关键字不是必需的。这个签名也会暴露出相同的行为:public static &lt;T&gt; void copy(List&lt;T&gt; dest, List&lt;? extends T&gt; src) {
  • @Nick 不错。为什么这个签名呢?这个问题应该向 Java 语言设计者提出。到目前为止,我发现的唯一原因是您可以编写如下代码:Collections.&lt;Mammal&gt;copy(animals, cats); - 但我不知道为什么有人会/应该编写这样的代码......
【解决方案3】:

例如,查看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&lt;T&gt;Collection&lt;? super T&gt; 更严格。


另一个例子:

Java 中的Predicate&lt;T&gt; 接口,在以下方法中使用&lt;? super T&gt; 通配符:

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

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

&lt;? super T&gt; 允许链接更广泛的不同谓词,例如:

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

【讨论】:

  • 我不认为这个例子特别引人注目。如果省略类型变量的显式实例化,则可以使用任一方法签名编写 Collections.addAll(nums, 1, 2, 3)
【解决方案4】:

假设你有一个方法:

passToConsumer(Consumer<? super SubType> consumer)

然后你用任何可以消耗SubTypeConsumer调用这个方法:

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&lt;Animal&gt;List&lt;Dog&gt;

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

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

如果您将putInto(List&lt;? super Dog&gt; list) 方法更改为putInto(List&lt;Animal&gt; 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&lt;Dog&gt; list):

Dog dog = new Dog();

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

【讨论】:

  • 无论何时你说Consumerconsume,这都会自动归入PECS 类别,但并不是说这很糟糕,而是很好的例子
【解决方案5】:

收藏品就是一个很好的例子。

1 中所述,List&lt;? super T&gt; 允许您创建 List,它将包含比 T 更少派生的类型元素,因此它可以包含从 T 继承的元素,即类型TT 继承自。

另一方面,List&lt;? extends T&gt; 允许您定义一个 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&lt;? super T&gt; 向我们保证来自src 的所有元素都将在新集合中有效。

1 : Difference between <? super T> and <? extends T> in Java

【讨论】:

    【解决方案6】:

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

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

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

    【讨论】:

      【解决方案7】:

      考虑这个简单的例子:

      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&lt;? super E&gt;。如果它只需要一个Comparator&lt;E&gt;(在这种情况下ENumber),那么代码将无法编译,因为Comparator&lt;Object&gt; 不是Comparator&lt;Number&gt; 的子类型(由于您的问题表明您的原因已经明白了,就不赘述了)。

      【讨论】:

        【解决方案8】:

        假设你有:

        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
        

        【讨论】:

          【解决方案9】:

          为此,我想到了一些现实生活中的例子。我想提出的第一个是现实世界中的对象被用于“即兴”功能的想法。想象一下你有一把套筒扳手:

          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 将有权调用像 applyTorque(100).withRotation("clockwise").withSocketSize(14) 这样的方法,如果它被用作 SocketWrench 而不仅仅是 Wrench,而不是 Hammer

          【讨论】:

          • 我不觉得这很有说服力:为什么 SocketWrench 会是通用的?
          • 您可以拥有各种套筒扳手类型:1/4” 驱动、1/2” 驱动、可调手柄角度套筒扳手、扭矩扳手、不同棘轮齿数等。但如果您只是需要棘轮扳手,您可以使用通用 SocketWrench 而不是特定的 3/4 英寸驱动 32 齿角柄套筒扳手。
          猜你喜欢
          • 1970-01-01
          • 2010-10-10
          • 2011-06-05
          • 1970-01-01
          • 2016-08-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多