我知道<? 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));
}
}
我正在寻找一个实际用例的示例,该示例可以在其中使用,而不是在解释它是什么。
答案 0 :(得分:46)
我能想到的最简单的例子是:
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) {
....
}
答案 1 :(得分:14)
假设您具有以下类层次结构: 猫从哺乳动物继承而来,而哺乳动物又从动物那里继承。
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 :(得分:5)
例如,查看Collections.addAll
方法实现:
ec2:Describe*
这里,可以将元素插入元素类型为元素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>
更具限制性。
另一个示例:
Java中的 Predicate<T>
接口,该接口在以下方法中使用Collection<? super T>
通配符:
<? super T>
default Predicate<T> and(Predicate<? super T> other);
default Predicate<T> or(Predicate<? super T> other);
允许链接更大范围的不同谓词,例如:
<? super T>
答案 3 :(得分:3)
假设您有一个方法:
passToConsumer(Consumer<? super SubType> consumer)
然后使用任何可能消耗Consumer
的{{1}}调用此方法:
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)
答案 4 :(得分:1)
我写了一个网络广播,所以有了类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>
。添加结果的地方。
这就是通用对话如何处理具体实现的方式,而其余代码则可以在超类上工作。
希望可以使它更加清晰。
答案 5 :(得分: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>
不是{{1}的子类型}(由于您的问题表明您已经理解的原因,因此我不再赘述)。
答案 6 :(得分:1)
这里的收藏就是一个很好的例子。
如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
中的所有元素在新集合中均有效。
答案 7 :(得分:0)
说你有
Kunde
然后:
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
答案 8 :(得分: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)
而不是SocketWrench
,则可以访问像Wrench
这样的方法Hammer
。