什么是通用 < 的真实例子?超级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>
(在这种情况下 E
是 Number
),那么代码将无法编译,因为 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 : 和
我了解 <? 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>
(在这种情况下 E
是 Number
),那么代码将无法编译,因为 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 : 和