回答:
tl; dr:「PECS」はコレクションの観点からのものです。ジェネリックコレクションからアイテムをプルするだけの場合、それはプロデューサーであり、使用する必要がありますextends
。アイテムを詰め込むだけの場合、それは消費者であり、使用する必要がありますsuper
。同じコレクションで両方を行う場合は、extends
またはを使用しないでくださいsuper
。
もののコレクションをパラメータとして受け取るメソッドがあるが、単に Collection<Thing>
。
ケース1:コレクションを調べて、各アイテムで処理を行います。
次に、リストはプロデューサーなので、使用する必要がありますCollection<? extends Thing>
ます。
その理由は、Collection<? extends Thing>
はの任意のサブタイプを保持できるため、操作を実行するThing
と各要素はとして動作するというThing
ことです。(実際にはに何も追加Collection<? extends Thing>
できません。実行時に特定のサブタイプを知ることができないためです。Thing
コレクションの保持さ。)
ケース2:コレクションにアイテムを追加したい。
次に、リストはコンシューマなので、使用する必要がありますCollection<? super Thing>
ます。
ここでの推論は、とは異なりCollection<? extends Thing>
、実際のパラメーター化された型が何であってCollection<? super Thing>
も常に保持できるということThing
です。ここでは、a Thing
を追加できる限り、リストにすでに何が含まれているかは気にしません。これが? super Thing
保証するものです。
doSomethingWithList(List list)
、あなたがしているがかかりリストをので、共分散が必要になります/拡張(または不変リスト)。一方、メソッドがのList doSomethingProvidingList
場合は、リストを生成するため、反変/スーパー(または不変リスト)が必要になります。
const
C ++でメソッドパラメータとしてへの参照を使用して、メソッドが引数を変更しないことを示すのと同様に、読みやすさを向上させる手段にすぎませんか?
コンピュータサイエンスにおけるこれの背後にある原則は、
? extends MyClass
、? super MyClass
とMyClass
下の写真はその概念を説明しているはずです。写真提供:Andrey Tyukin
PECS(プロデューサーextends
およびコンシューマーsuper
)
ニーモニック→Get and Put原則。
この原則は次のように述べています:
Javaの例:
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object
}
Liskov置換の原則: SがTのサブタイプである場合、T型のオブジェクトはS型のオブジェクトに置き換えられます。
プログラミング言語の型システム内では、型付け規則
この一般的な現象を説明するために、配列タイプを検討してください。動物タイプの場合、動物タイプを作成できます[]
Javaの例:
Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)
List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
境界付き(つまり、どこかに向かっている)ワイルドカード:ワイルドカードには3つの異なる種類があります。
?
または? extends Object
- 無制限ワイルドカード。それはすべてのタイプの家族を表しています。取得と配置の両方で使用します。? extends T
サブタイプであるすべてのタイプのファミリー T
)- 上限付きのワイルドカード。T
あるアッパー -ほとんどのクラスは、継承階層で。構造体から値extends
のみを取得する場合は、ワイルドカードを使用します。? super T
スーパータイプであるすべてのタイプのファミリー T
)- 下限のあるワイルドカード。T
ある低継承階層の-ほとんどのクラスは。値を構造体にsuper
のみ入れる場合は、ワイルドカードを使用します。注:ワイルドカード?
は0回または1回を意味し、不明なタイプを表します。ワイルドカードは、パラメーターのタイプとして使用できますが、ジェネリックメソッドの呼び出し、ジェネリッククラスインスタンスの作成のタイプ引数として使用することはできません(つまり、使用するようなプログラムの他の場所で使用されていない参照をワイルドカードとして使用した場合T
)
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class Test {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape shape= list.get(0);//compiles so list act as produces only
/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/
}
/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object object= list.get(0); // gets an object, but we don't know what kind of Object it is.
/*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
List <?>またはList <?に要素を追加できません Object>を拡張しているので、なぜそれができるのかわかりませんUse when you both get and put
。
?
-「無制限のワイルドカード」-不変性の正反対に対応します。次のドキュメントを参照してください:docs.oracle.com/javase/tutorial/java/generics/…これは、コードが「in」変数と「out」変数の両方として変数にアクセスする必要がある場合は、次のようにします。ワイルドカードを使用しないでください。(彼らは「取得」と「プット」の同義語として「in」と「out」を使用しています)。を除いnull
て、でパラメーター化されたコレクションに追加することはできません?
。
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void testCoVariance(List<? extends B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b); // does not compile
myBlist.add(c); // does not compile
A a = myBlist.get(0);
}
public void testContraVariance(List<? super B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0); // does not compile
}
}
? extends B
BとBを拡張するものを意味
私の別の質問への回答で説明するように、PECSは、ジョシュ・ブロッホによって作成されたP roducer extends
、C onsumer を覚えやすくするためのニーモニックデバイスですsuper
。
これは、メソッドに渡されるパラメーター化された型が
T
(何らかの方法でそこから取得される)のインスタンスを生成する場合は、? extends T
を使用する必要があります。これは、のサブクラスのインスタンスT
もであるためT
です。メソッドに渡されるパラメーター化された型がのインスタンスを消費する場合
T
(何かに実行するために渡される)は、のスーパータイプを受け入れる任意のメソッドにの? super T
インスタンスをT
合法的に渡すことができるため、使用する必要がありますT
。Comparator<Number>
A上で使用することができCollection<Integer>
、たとえば、。? extends T
はでComparator<Integer>
操作できないため、機能しませんCollection<Number>
。
一般的にだけ使用する必要があることを注意? extends T
して? super T
、いくつかのメソッドのパラメータのために。メソッドはT
、ジェネリック戻り値型の型パラメーターとして使用するだけです。
簡単に言えば、PECSを覚えるための3つの簡単なルール:
<? extends T>
タイプのオブジェクトを取得する必要がある場合は、ワイルドカードを使用しますT
。<? super T>
タイプのオブジェクトをT
コレクションに入れる必要がある場合は、ワイルドカードを使用します。この階層を想定してみましょう:
class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C
PEを明確にしましょう-Producer Extends:
List<? extends Shark> sharks = new ArrayList<>();
このリストに「サメ」を拡張するオブジェクトを追加できないのはなぜですか?お気に入り:
sharks.add(new HammerShark());//will result in compilation error
実行時にタイプA、B、またはCになる可能性のあるリストがあるため、Javaで許可されていない組み合わせになる可能性があるため、タイプA、B、またはCのオブジェクトをリストに追加することはできません。
実際には、コンパイラは実際にBを追加したことをコンパイル時に確認できます。
sharks.add(new HammerShark());
...しかし、実行時にBがリストタイプのサブタイプまたはスーパータイプになるかどうかを判断する方法はありません。実行時にリストのタイプはタイプA、B、Cのいずれかになります。したがって、たとえばDeadHammerSharkのリストにHammerSkark(スーパータイプ)を追加することはできません。
*「OKですが、HammerSkarkは最小タイプなので追加できないのはなぜですか?」と言うでしょう。回答:それはあなたが知っている最小のものです。しかし、HammerSkarkは他の誰かが拡張することもでき、同じシナリオになります。
CSを明確にしましょう-コンシューマースーパー:
同じ階層でこれを試すことができます:
List<? super Shark> sharks = new ArrayList<>();
このリストに追加できるのはなぜですか?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
shark(A、B、C)の下にあるものは常にshark(X、Y、Z)より上のもののサブタイプになるため、上記のタイプのオブジェクトを追加できます。わかりやすい。
実行時に追加されたオブジェクトのタイプは、リストの宣言されたタイプ(X、Y、Z)よりも階層が高くなる可能性があるため、 Sharkの上にタイプを追加することはできません。これは許可されていません。
しかし、なぜあなたはこのリストから読むことができないのですか?(つまり、要素を取得することはできますが、それをオブジェクトo以外に割り当てることはできません):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
実行時に、リストのタイプはAより上の任意のタイプにすることができます:X、Y、Z、...コンパイラーは割り当てステートメントをコンパイルできます(これは正しいようです)が、実行時にはにs(動物)のタイプはリストの宣言されたタイプ(Creatureまたはそれ以上である可能性があります)よりも階層。これは許可されていません。
総括する
私たちは、使用<? super T>
のタイプのオブジェクトは以下で追加することT
にList
。読み込めません。リストから以下のタイプのオブジェクトを読み取る
ために使用<? extends T>
しますT
。要素を追加することはできません。
(ジェネリックのワイルドカードを使用した十分な例がないため、回答を追加します)
// Source
List<Integer> intList = Arrays.asList(1,2,3);
List<Double> doubleList = Arrays.asList(2.78,3.14);
List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
for(T n : src){
dest.add(n);
}
}
// Let's try to copy intList to its supertype
copyElements1(intList,numList2); // error, method signature just says "T"
// and here the compiler is given
// two types: Integer and Number,
// so which one shall it be?
// PECS to the rescue!
copyElements2(intList,numList2); // possible
// copy Integer (? extends T) to its supertype (Number is super of Integer)
private static <T> void copyElements2(Collection<? extends T> src,
Collection<? super T> dest) {
for(T n : src){
dest.add(n);
}
}
これは、私が拡張とスーパーを考える上で最も明確で最も簡単な方法です。
extends
読書用です
super
以下のためである書き込み
私は「PECS」は誰が「プロデューサー」で誰が「コンシューマー」であるかについて物事を考えるための自明ではない方法だと思います。「PECS」は、データコレクション自体の観点から定義されます。コレクションは、オブジェクトがそこに書き込まれている場合は「消費」し(コードの呼び出しからオブジェクトを消費します)、オブジェクトがそこから読み取られている場合は「生成」します(それはいくつかの呼び出しコードに対してオブジェクトを生成しています)。これは、他のすべての名前の付け方に反しています。標準のJava APIは、コレクション自体ではなく、呼び出しコードの観点から名前が付けられています。たとえば、java.util.Listのコレクション中心のビューには、「add()」ではなく「receive()」という名前のメソッドが必要です–結局のところ、要素ですが、リスト自体要素を受け取ります。
コレクションとやり取りするコードの観点から物事を考える方が、より直感的で自然で一貫していると思います。コードはコレクションから「読み取り」または「書き込み」しますか?その後、コレクションに書き込むコードは「プロデューサー」になり、コレクションから読み取るコードは「コンシューマー」になります。
src
、やのようなコレクションを作成/消費するための名前を持っていることがよくありますdst
。つまり、コードとコンテナの両方を同時に扱っているのですが、私はそれらについて考えました。「コードを消費する」は生成中のコンテナから消費し、「コードを生成する」は消費するコンテナのために生成します。
PECSの「ルール」は、以下が合法であることを保証するだけです。
?
、合法的に参照できます T
?
、法的に参照することができます T
に沿った典型的なペアリングList<? extends T> producer, List<? super T> consumer
は、コンパイラが標準の「IS-A」継承関係ルールを適用できるようにするだけです。もし合法的にそうすることができれば、言うのはもっと簡単かもしれません<T extends ?>, <? extends T>
(あるいは、Scalaではもっと上にあるように、上で見られるように、それ[-T], [+T]
はそうです<? super T>, <? extends T>
。残念ながら私たちにできる最善のことはです。
私が最初にこれに遭遇し、頭の中でそれを壊したとき、メカニズムは理にかなっていますが、コード自体は私を混乱させ続けました-私は「境界はそのように反転する必要はないように思われる」と考え続けました-私は上記のことは明らかでした-それは単に標準の参照規則への準拠を保証することについてです。
私を助けたのは、類推として通常の割り当てを使用してそれを見ていたことです。
次の(本番用ではない)おもちゃのコードを検討してください。
// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
for(T t : producer)
consumer.add(t);
}
以下のために、割り当て類似の用語でこれを示す割り当ての「左側」 - -と基準ワイルドカード(未知のタイプ)されているどのようなことを保証され、「A-IS」- それに割り当てることができる理由は、スーパータイプ(または多くても同じタイプ)ですconsumer
?
<? super T>
?
T
?
T
?
T
です。
producer
懸念、それはちょうど逆のと同じである:producer
の?
ワイルドカード(不明なタイプ)があるリファレント -割り当ての『右側』 -と<? extends T>
どんなことが保証?
され、?
『-Aは、IS』T
-ということ、それが割り当てることができ、AにはT
、理由?
としてサブタイプ(または少なくとも同じタイプ)ですT
。
共分散:サブタイプを受け入れる
反変:スーパータイプを受け入れる
共変タイプは読み取り専用ですが、反変タイプは書き込み専用です。
例を見てみましょう
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
ジェネリックスを使用すると、安全な方法で動的に型を操作できます
//ListA
List<A> listA = new ArrayList<A>();
//add
listA.add(new A());
listA.add(new B());
listA.add(new C());
//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();
//add
listB.add(new B());
//get
B b0 = listB.get(0);
結果としてJavaのコレクションは参照型であるため、次の問題があります。
問題#1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
※Swiftのジェネリックはコレクションが【約】なので問題ありませんValue type
なのでので新しいコレクションを作成します
問題#2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
ワイルドカードは参照タイプの機能であり、直接インスタンス化することはできません
ソリューション#1
<? super A>
別名下限、つまりコンバリアンス、つまりコンシューマは、Aとすべてのスーパークラスによって動作することを保証するため、追加しても安全です。
List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();
//add
listSuperA.add(new A());
listSuperA.add(new B());
//get
Object o0 = listSuperA.get(0);
ソリューション#2
<? extends A>
別名上限、つまり共分散、つまりプロデューサーは、Aおよびすべてのサブクラスによって操作されることを保証します。そのため、取得してキャストしても安全です。
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
です。