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


76

我知道这<? super T>代表任何级别的T父类T(任何级别的父类)。但是我真的很难想象这个通用绑定通配符的任何现实示例。

我了解这<? 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));
  }
}

我正在寻找可以使用此构造的现实生活用例的示例,而不是对其含义的解释。


18
这不是一个重复的,非常有效的问题
尤金(Eugene)

6
我也不认为这是重复的,他要求的是具体情况,而不是背后的原则
ItFreak

2
这是我在关闭本书时要写的答案的开始:我在某种程度上同意了不赞成投票的人:可以在一定的努力下从stackoverflow.com/questions/2723397/…中得出答案。。但是,这个问题(以及此处的答案)都集中在技术原理上。一个简单,现实的示例说明在哪里使用有意义? super T
Marco13年

4
不要以为这应该是重复的,因为作者要求的是OOP的真实模型,而不是深入解释Java中的继承方式。
约翰·史塔克

3
这里的例子不是真实的用例吗?
user253751'9

Answers:


49

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

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

显然,有一个辅助方法需要aList并将其元素排到a 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) {
    ....
}

1
科特林教程使用Source的例子...这基本上是双您的Sink例子。
巴库里

2
当然可以通过创建列表以相反的方式定义它吗?延伸T?
Weckar E.

@WeckarE。如果方法本身在列表中,则不行。
恢复莫妮卡

1
@WeckarE。我可以,但是这会稍微改变语义,现在我什么都不能添加list,它只能成为生产者。如前所述,这是一个简化的示例……
尤金(Eugene)

17

假设您具有此类的层次结构:Cat继承自哺乳动物,而哺乳动物又继承自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

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


2
不过,使用此super关键字并不是必需的。此签名也将暴露相同的行为: public static <T> void copy(List<T> dest, List<? extends T> src) {
尼克

1
@尼克好抓住。为什么要这样签名呢?这个问题应该向Java语言设计者提出。到目前为止,我发现的唯一原因是您可以编写如下代码:Collections.<Mammal>copy(animals, cats);-但我不知道为什么有人会/应该这样编写代码……
Benoit

6

例如,研究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

我认为这个例子不是特别引人注目。如果省略了类型变量的显式实例化,则可以Collections.addAll(nums, 1, 2, 3)使用任一方法签名进行编写。
尼克

2

假设您有一个方法:

passToConsumer(Consumer<? super SubType> consumer)

然后使用任何Consumer可能消耗的方法调用此方法SubType

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>

1
每当您说Consumerconsume这自动归为一PECS类时,虽然不是说这很不好,但还是很好的例子
Eugene

1

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

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

这节课有一个方法public T getSelectedStream()
因此,呼叫者收到的T是具体类型(PLS或M3U),但需要在超类上工作,因此有一个列表:List<T super MetaInformationObject>。将结果添加到的位置。
这就是通用对话如何处理具体的实现,而其余代码可以在超类上工作的方式。
希望这使它更加清晰。


1

考虑以下简单示例:

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

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

这是因为integers::sort需要编译Comparator<? super E>。如果只用了Comparator<E>E在这种情况下为Number),则代码将无法编译,因为Comparator<Object>它不是的子类型Comparator<Number>(由于您的问题表明您已经理解,因此我不再赘述)。


1

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

1中所述List<? super T>您可以创建List,以容纳类型比少的T元素,从而可以容纳继承自的元素T,其类型为TT从。

另一方面,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<?之间的区别 超级T>和<?在Java中扩展T>


0

说您有:

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

0

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

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)将其用作aSocketWrench而不是a Wrench,而不是a Hammer


我觉得这不是很吸引人:为什么SocketWrench是通用的?
马克斯

您可以使用各种套筒扳手类型:1/4英寸驱动器,1/2英寸驱动器,角度可调的套筒扳手,扭矩扳手,棘轮齿数不同等。但是,如果您只需要棘轮扳手,则可以使用使用通用的SocketWrench代替特定的3/4英寸驱动器32齿弯柄套筒扳手。
约翰·史塔克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.