web-dev-qa-db-ja.com

PECS(Producer Extends Consumer Super)とは何ですか?

総称を読みながら、PECS(プロデューサーextendsおよびコンシューマーsuperの略)に出会いました。

extendssuperの混乱を解決するためにPECSを使用する方法を誰かに説明できますか?

651
peakit

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が保証するものです。

752
Michael Myers

この背後にあるコンピュータサイエンスの原則は、

  • 共分散:? extends MyClass
  • 逆分散:? super MyClass
  • 不変/不一致:MyClass

下の図はその概念を説明しているはずです。

画像提供: Andrey Tyukin

Covariance vs Contravariance

504
anoopelias

PECS( "Producer extends and Consumer super"の略)は、次のように説明されます。Get and Put Principle

原則を取得して(Javaの汎用およびコレクションから)

それは述べています、

  1. 構造体からgetの値のみを使用する場合は、ワイルドカードを拡張するを使用します。
  2. スーパーワイルドカードを使用するのは、putの値だけを構造体に入れてください。
  3. ワイルドカードを使用しないでくださいあなたがいる場合はgetとputの両方

例で理解しましょう。

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);
}
150
Prateek

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のオブジェクトに置き換えられます。

プログラミング言語の型システム内で、入力規則

  • covariant型の順序を保持する場合(≤)、型をより具体的なものからより一般的なものに順序付けます。
  • convarivariantこの順序を逆にする場合;
  • invariantまたはこれらのいずれも当てはまらない場合は非バリアント。

共分散と反分散

  • 読み取り専用のデータ型(ソース)は、共変;
  • 書き込み専用のデータ型(シンク)は、contravariantになります。
  • ソースとシンクの両方として機能する可変データ型は、invariantでなければなりません。

この一般的な現象を説明するために、配列タイプを検討してください。タイプAnimalの場合、Animal []タイプを作成できます。

  • covariant:Cat []はAnimal []です。
  • 反変:Animal []はCat []です。
  • invariant:Animal []はCat []ではなく、Cat []は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 TTのサブタイプであるすべてのタイプのファミリー)-upper boundのワイルドカード。 Tは、継承階層のupper-mostクラスです。構造体からGet値のみを取得する場合は、extendsワイルドカードを使用します。
  • 反分散:? super TTのスーパータイプであるすべてのタイプのファミリー)-下限のワイルドカード。 Tは、継承階層のlower-mostクラスです。値を構造体にPutする場合のみ、superワイルドカードを使用します。

注:ワイルドカード?は、ゼロまたは1回を意味し、不明なタイプを表します。ワイルドカードはパラメーターの型として使用でき、ジェネリックメソッドの呼び出し、ジェネリッククラスインスタンスの作成のタイプ引数として使用されることはありません(つまり、Tを使用するようなプログラムの他の場所では使用されないワイルドカードを使用する場合)

enter image description here

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.
        */  
    }
}

generics および examples

37
Premraj
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
    }
}
28
Gab

私が別の質問に対して 私の答え で説明したように、PECSはPを覚えやすくするためにJosh Blochによって作成されたニーモニックデバイスです。 roducer extendsCsuper

これは、メソッドに渡されるパラメータ化された型がTproduceインスタンスを生成するとき(何らかの方法でそれらから取得される)、? extends Tが使用されるべきであることを意味します。 of TTです。

メソッドに渡されるパラメータ化された型がTconsume個のインスタンス(何かをするためにそれに渡される)の場合、Tのインスタンスは可能であるため? super Tを使うべきです。 Tのスーパータイプを受け入れるメソッドには、合法的に渡すことができます。たとえば、Comparator<Number>Collection<Integer>で使用できます。 ? extends TComparator<Integer>を操作できなかったため、Collection<Number>は機能しませんでした。

通常、? extends T? super Tはメソッドのパラメータとしてのみ使用するようにしてください。メソッドは一般的な戻り型の型パラメータとしてTを使用するだけです。

22
ColinD

一言で言えば、PECSを覚えるための3つの簡単な規則:

  1. コレクションからT型のオブジェクトを取得する必要がある場合は、<? extends T>ワイルドカードを使用してください。
  2. コレクションにタイプTのオブジェクトを入れる必要がある場合は、<? super T>ワイルドカードを使用してください。
  3. 両方を満たす必要がある場合は、ワイルドカードを使用しないでください。それと同じくらい簡単です。
19

(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);
        }
    }
7
Andrejs

この階層を仮定しましょう:

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>を使用します。要素を追加することはできません。

6
Daniel

これを覚えて:

消費者が食べる夕食(超);プロデューサー拡張彼の親の工場

2
Jason

共分散:サブタイプを受け入れます
Contravariance:スーパータイプを受け入れます

共変型は読み取り専用ですが、反変型は書き込み専用です。

1
Farrukh Chishti

実生活の例を使用して(いくつかの単純化を伴って):

  1. リストと同じように、貨車で貨物列車を想像してみてください。
  2. あなたがすることができます置く貨物がある場合、貨物車の中で同じサイズまたは小さいサイズ貨物車より== <? super FreightCarSize>
  3. デポに十分な場所(貨物のサイズ以上)があれば、荷降ろし貨物車からの貨物を取り出すことができます= <? extends DepotSize>
0
contrapost