総称を読みながら、PECS(プロデューサーextends
およびコンシューマーsuper
の略)に出会いました。
extends
とsuper
の混乱を解決するためにPECSを使用する方法を誰かに説明できますか?
tl; dr: "PECS"はコレクションの観点からのものです。あなたがonlyジェネリックコレクションからアイテムを引き出すのであれば、それはプロデューサーであり、あなたはextends
を使うべきです。あなたがonlyアイテムを詰め込むのであれば、それは消費者であり、あなたはsuper
を使うべきです。同じコレクションで両方を行う場合は、extends
またはsuper
を使用しないでください。
パラメータとして物の集まりを取るメソッドがあるとしますが、Collection<Thing>
を受け入れるよりも柔軟にしたいとします。
ケース1:あなたはコレクションを調べて、それぞれのアイテムで何かをしたいです。
するとリストはプロデューサーになりますので、Collection<? extends Thing>
を使用してください。
その理由は、Collection<? extends Thing>
はThing
の任意のサブタイプを保持できるため、操作を実行すると各要素がThing
として動作するためです。 (実行時にコレクションが保持しているspecificThing
のサブタイプがわからないため、実際にはCollection<? extends Thing>
には何も追加できません。)
ケース2:あなたはコレクションにものを追加したいです。
それからリストは消費者なので、あなたはCollection<? super Thing>
を使うべきです。
ここでの推論は、Collection<? extends Thing>
とは異なり、Collection<? super Thing>
は実際のパラメータ化された型が何であっても常にThing
を保持できるということです。ここでThing
を追加できるのであれば、リストにあるものは何でも構いません。これが? super Thing
が保証するものです。
この背後にあるコンピュータサイエンスの原則は、
? extends MyClass
、? super MyClass
とMyClass
下の図はその概念を説明しているはずです。
画像提供: Andrey Tyukin
PECS( "Producer extends
and Consumer super
"の略)は、次のように説明されます。Get and Put Principle
それは述べています、
1。ワイルドカードの拡張(値の取得、すなわちProducer extends
)
これは数の集まりを取り、それぞれをdouble
に変換してそれらを合計するメソッドです。
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums)
s += num.doubleValue();
return s;
}
メソッドを呼び出しましょう:
List<Integer>ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double>doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;
sum()
メソッドはextends
を使用しているので、以下の呼び出しはすべて正当です。最初の2つの呼び出しは、extendが使われていなければ合法ではありません。
EXCEPTION:あなたなにも入れることはできませんを宣言した型にextends
ワイルドカード - すべての参照型に属する値null
を除く:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null); // ok
assert nums.toString().equals("[1, 2, null]");
2。スーパーワイルドカードの場合(値を入れる、つまりConsumer super
)
これは、数字のコレクションとint n
を受け取り、最初のn
の整数を0から始めてコレクションに入れるメソッドです。
public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}
メソッドを呼び出しましょう:
List<Integer>ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number>nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object>objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");
count()
メソッドはsuper
を使用しているので、以下の呼び出しはすべて正当です。superが使用されていない場合、最後の2つの呼び出しは正当ではありません。
EXCEPTION:あなたなにも得られないで宣言された型からsuper
ワイルドカード - すべての参照型のスーパータイプであるObject
型の値を除く。
List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");
。GetとPutの両方でワイルドカードを使用しないでください
両方ともの値をとgetの値を同じ構造の外に入れる(=入れる)ときはいつでもワイルドカードを使うべきではない。
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
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
}
リスコフ置換原理: SがTのサブタイプである場合、タイプTのオブジェクトはタイプSのオブジェクトに置き換えられます。
プログラミング言語の型システム内で、入力規則
この一般的な現象を説明するために、配列タイプを検討してください。タイプAnimalの場合、Animal []タイプを作成できます。
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
bounded(つまりどこかに向かっている)wildcard:3つの異なるフレーバーがありますワイルドカードの:
?
または? extends Object
-Unboundedワイルドカード。それはすべてのタイプの家族を表しています。あなたが両方を取得し、置くときに使用します。? extends T
(T
のサブタイプであるすべてのタイプのファミリー)-upper boundのワイルドカード。 T
は、継承階層のupper-mostクラスです。構造体からGet値のみを取得する場合は、extends
ワイルドカードを使用します。? super T
(T
のスーパータイプであるすべてのタイプのファミリー)-下限のワイルドカード。 T
は、継承階層のlower-mostクラスです。値を構造体にPutする場合のみ、super
ワイルドカードを使用します。注:ワイルドカード?
は、ゼロまたは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.
*/
}
}
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
}
}
私が別の質問に対して 私の答え で説明したように、PECSはPを覚えやすくするためにJosh Blochによって作成されたニーモニックデバイスです。 roducer extends
、Csuper
。
これは、メソッドに渡されるパラメータ化された型が
T
のproduceインスタンスを生成するとき(何らかの方法でそれらから取得される)、? extends T
が使用されるべきであることを意味します。 ofT
もT
です。メソッドに渡されるパラメータ化された型が
T
のconsume個のインスタンス(何かをするためにそれに渡される)の場合、T
のインスタンスは可能であるため? super T
を使うべきです。T
のスーパータイプを受け入れるメソッドには、合法的に渡すことができます。たとえば、Comparator<Number>
はCollection<Integer>
で使用できます。? extends T
はComparator<Integer>
を操作できなかったため、Collection<Number>
は機能しませんでした。
通常、? extends T
と? super T
はメソッドのパラメータとしてのみ使用するようにしてください。メソッドは一般的な戻り型の型パラメータとしてT
を使用するだけです。
一言で言えば、PECSを覚えるための3つの簡単な規則:
T
型のオブジェクトを取得する必要がある場合は、<? extends T>
ワイルドカードを使用してください。T
のオブジェクトを入れる必要がある場合は、<? super T>
ワイルドカードを使用してください。(Genericsワイルドカードを使用した例が十分にないため、回答を追加してください)
// 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);
}
}
この階層を仮定しましょう:
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を明確にしましょう-プロデューサーの拡張:
List<? extends Shark> sharks = new ArrayList<>();
このリストに「Shark」を拡張するオブジェクトを追加できないのはなぜですか?のような:
sharks.add(new HammerShark());//will result in compilation error
タイプA、B、またはCにできるリストがあるので、実行時、タイプA、B、またはCのオブジェクトを追加することはできません。 Javaで許可されています。
実際には、コンパイラはコンパイル時にBを追加したことを実際に確認できます。
sharks.add(new HammerShark());
...しかし、実行時にBがリスト型のサブタイプまたはスーパータイプになるかどうかを判断する方法はありません。実行時には、リストタイプはA、B、Cのいずれかのタイプになります。したがって、たとえばDeadHammerSharkのリストにHammerSkark(スーパータイプ)を追加することはできません。
*あなたは言うでしょう:「しかし、なぜそれが最小のタイプであるので、HammerSkarkをそれに追加できないのですか?」。回答:最小ですyo知っています。しかし、HammerSkarkは他の誰かが拡張することもでき、同じシナリオになります。
CS-コンシューマースーパーを明確にしましょう:
同じ階層でこれを試すことができます:
List<? super Shark> sharks = new ArrayList<>();
何となぜcanこのリストに追加しますか?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Shark(A、B、C)の下にあるものは常にshark(X、Y、Z)の上にあるもののサブタイプになるため、上記のタイプのオブジェクトを追加できます。わかりやすい。
実行不可 Sharkの上にタイプを追加します。これは実行時追加されたオブジェクトのタイプは、リストの宣言されたタイプ(X、Y、Z)よりも階層が高くなる可能性があるためです。これは許可されていません。
しかし、なぜこのリストから読むことができないのですか? (つまり、要素を取り出すことができますが、オブジェクトo以外に割り当てることはできません):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
実行時に、リストのタイプはAより上の任意のタイプになります:X、Y、Z、...コンパイラは割り当てステートメントをコンパイルできます(これは正しいと思われます)が、実行時のタイプ(Animal)は、リストの宣言されたタイプ(Creatureまたはそれ以上)の階層よりも低い階層にすることができます。これは許可されていません。
要約すると
リストのT以下のタイプのオブジェクトを追加するために<? super T>
を使用します。そこから読み取ることはできません。
リストからT以下の型のオブジェクトを読み取るために<? extends T>
を使用します。要素を追加することはできません。
これを覚えて:
消費者が食べる夕食(超);プロデューサー拡張彼の親の工場
共分散:サブタイプを受け入れます
Contravariance:スーパータイプを受け入れます
共変型は読み取り専用ですが、反変型は書き込み専用です。
実生活の例を使用して(いくつかの単純化を伴って):
<? super FreightCarSize>
<? extends DepotSize>