<? 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));
}
}
私は実生活のユースケースの例を探していますが、この構造を使用することができますが、それが何であるかの説明ではありません。
私が考えることができる最も簡単な例は次のとおりです。
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
同じCollections
から取得されます。このようにDog
はComparable<Animal>
を実装でき、Animal
がすでに実装している場合、Dog
は何もする必要がありません。
実際の例では編集:
Ping-pongsをメールで送信した後、職場から実際の例を提示することができます(はい!)。
Sink
と呼ばれるインターフェイスがあります(何をするかは関係ありません)、アイデアはaccumulates事です。宣言は非常に簡単です(単純化されています):
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) {
....
}
このクラス階層があるとします: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
しかし、これらの呼び出しはnot有効です:
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
したがって、メソッドシグネチャは、より具体的な(継承階層の下位)クラスからより一般的なクラス(継承階層の上位)にコピーすることを保証するだけであり、逆ではありません。
たとえば、 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
メソッドがあるとします:
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>
この簡単な例を考えてみましょう。
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>
のサブタイプではないため、コードはコンパイルに失敗します(質問が既に理解していることを示しているため、私は入りません) )。
ウェブラジオを書いたので、クラス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<T super MetaInformationObject>
。結果が追加された場所。
これは、一般的なダイアログが具体的な実装をどのように処理し、残りのコードがスーパークラスで機能するかということです。
もう少し明確になることを願っています。
ここでは、コレクションが良い例となります。
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
のすべての要素が新しいコレクションで有効であることを保証します。
いくつかの実際の例がこのために思い浮かびます。最初に取り上げたいのは、「即興」機能に使用される現実世界のオブジェクトのアイデアです。ソケットレンチがあるとします。
public class SocketWrench <T extends Wrench>
ソケットレンチの明白な目的は、Wrench
として使用されることです。ただし、ピンチでレンチを使用して釘を打つことができると考える場合、次のような継承階層を作成できます。
public class SocketWrench <T extends Wrench>
public class Wrench extends Hammer
このシナリオでは、SocketWrench
の特殊な使用と見なされる場合でも、socketWrench.pound(Nail nail = new FinishingNail())
を呼び出すことができます。
ずっと、SocketWrench
は、SocketWrench
ではなくWrench
の代わりにHammer
として使用されている場合、applyTorque(100).withRotation("clockwise").withSocketSize(14)
などのメソッドを呼び出すことができるようになります。
あなたが持っていると言う:
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