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


729

ジェネリックを読んでいるときにPECS(プロデューサーextendsとコンシューマーのsuper略)に出会いました。

extendsとの間の混乱を解決するためにPECSを使用する方法を誰かに説明できますかsuper


3
@ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630の例を使用した非常に優れた説明superです。
lupchiazoem

回答:


844

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


142
私は常にこのように考えています。プロデューサーはより具体的な何かを生成することが許可されているため、拡張されコンシューマーはより一般的なものを受け入れることができるため、スーパーです。
Feuermurmel 2013年

10
プロデューサー/コンシューマーの違いを思い出すもう1つの方法は、メソッドシグネチャを考えることです。あなたがメソッドを持っている場合はdoSomethingWithList(List list)、あなたがしているがかかりリストをので、共分散が必要になります/拡張(または不変リスト)。一方、メソッドがのList doSomethingProvidingList場合は、リストを生成するため、反変/スーパー(または不変リスト)が必要になります。
ラマン

3
@MichaelMyers:なぜこれら両方のケースでパラメーター化された型を使用できないのですか?ここでワイルドカードを使用する特定の利点はありますか、それとも、たとえばconstC ++でメソッドパラメータとしてへの参照を使用して、メソッドが引数を変更しないことを示すのと同様に、読みやすさを向上させる手段にすぎませんか?
チャタジー2014年

7
@Raman、私はあなたがそれを混乱させたと思います。doSthWithList(List <?super Thing>を持つことができます)では、コンシューマーであるため、superを使用できます(CSを思い出してください)。ただし、リストです<?(PE)を生成するときに、より具体的なものを返すことができるため、Thing> getList()を拡張します。
masterxilo 2014年

4
@AZ_私はあなたの感情を共有します。メソッドがリストからget()を実行する場合、メソッドはConsumer <T>と見なされ、リストはプロバイダーと見なされます。しかし、PECSのルールは「リストの観点から」であり、したがって「拡張」が必要です。GEPSである必要があります。getextends; スーパーを入れます。
Treefish Zhang

561

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

  • 共分散: ? extends MyClass
  • 逆分散: ? super MyClass
  • 不変性/非変動: MyClass

下の写真はその概念を説明しているはずです。写真提供:Andrey Tyukin

共分散と反分散


144
こんにちは、みなさん。私はAndrey Tyukinです。anoopelias&DaoWenから連絡があり、スケッチを使用する許可を取得したことを確認したいと思いました。これは(CC)-BY-SAに基づいてライセンスされています。Thx @ Anoopに2番目の命を与えました^^ @Brian Agnew:(「投票数が少ない」):これはScalaのスケッチであるため、Scala構文を使用し、Javaの奇妙な呼び出しとはかなり異なる宣言サイトの差異を想定しています-site variance ...多分私はこのスケッチがJavaにどのように適用されるかを明確に示すより詳細な答えを書く必要があります...
Andrey Tyukin

3
これは、私がこれまでに見つけた共分散と反変の最も単純で明確な説明の1つです。
cs4r 2017年

@Andrey Tyukinこんにちは、この画像も使用したいと思います。どうすれば連絡できますか?
slouc 2017年

あなたはこのイラストについてご質問がありましたら、私たちはチャットルームでそれらを議論することができます:chat.stackoverflow.com/rooms/145734/...
アンドレイTyukin


49

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

プログラミング言語の型システム内では、型付け規則

  • タイプの順序付けを維持する場合は共変(≤)、タイプをより具体的なものからより一般的なものに順序付けします。
  • この順序が逆の場合は反変
  • これらのいずれも該当しない場合は、不変または非変量

共分散と反分散

  • 読み取り専用のデータ型(ソース)は共変にすることができます。
  • 書き込み専用のデータ型(シンク)は反変である可能性があります。
  • ソースとシンクの両方として機能する可変データ型は不変である必要があります。

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

  • 共変:猫[]は動物[]です。
  • 反変:動物[]は猫[]です。
  • invariant:動物[]は猫[]ではなく、猫[]は動物[]ではありません。

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

ジェネリック


ねえ、私はあなたが最後の文であなたが何を意味しているかを知りたかっただけです:「あなたが私のアナロジーが間違っていると思うなら更新してください」。それが倫理的に間違っている(主観的)か、プログラミングのコンテキストで間違っている(これは客観的です:いいえ、間違っていません)か。私はそれを、文化的規範や倫理的信念から独立して普遍的に受け入れられるより中立的な例に置き換えたいと思います。それでよろしければ。
ニューロン2018

やっと手に入れることができた。いい説明。
Oleg Kuts

2
@Premraj、、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
LiuWenbin_NO。

1
@LiuWenbin_NO。-答えのその部分は誤解を招くものです。?-「無制限のワイルドカード」-不変性の正反対に対応します。次のドキュメントを参照してください:docs.oracle.com/javase/tutorial/java/generics/…これは、コードが「in」変数と「out」変数の両方として変数にアクセスする必要がある場合は、次のようにします。ワイルドカードを使用しないでください。(彼らは「取得」と「プット」の同義語として「in」と「out」を使用しています)。を除いnullて、でパラメーター化されたコレクションに追加することはできません?
mouselabs

29
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 extends」として解釈されるべきです。これはBが拡張するものであり、B自体を除くObjectまでのBのすべてのスーパークラスを含みます。コードをありがとう!
Saurabh Patil、2016年

3
@SaurabhPatilいいえ、? extends BBとBを拡張するものを意味
asgs

24

私の別の質問への回答で説明するように、PECSは、ジョシュ・ブロッホによって作成されたP roducer extendsC onsumer を覚えやすくするためのニーモニックデバイスですsuper

これは、メソッドに渡されるパラメーター化された型が T(何らかの方法でそこから取得される)のインスタンスを生成する場合は、? extends Tを使用する必要があります。これは、のサブクラスのインスタンスTもであるためTです。

メソッドに渡されるパラメーター化された型がのインスタンスを消費する場合T(何かに実行するために渡される)は、のスーパータイプを受け入れる任意のメソッドにの? super TインスタンスをT合法的に渡すことができるため、使用する必要がありますTComparator<Number>A上で使用することができCollection<Integer>、たとえば、。? extends TはでComparator<Integer>操作できないため、機能しませんCollection<Number>

一般的にだけ使用する必要があることを注意? extends Tして? super T、いくつかのメソッドのパラメータのために。メソッドはT、ジェネリック戻り値型の型パラメーターとして使用するだけです。


1
この原則はコレクションにのみ適用されますか?それをリストと相関させようとするのは理にかなっています。sort(List <T>、Comparator <?super T>)--->のシグネチャについて考える場合、ここではコンパレータがsuperを使用しているため、PECSコンテキストのコンシューマであることを意味します。たとえば次のような実装を見ると、public int compare(Person a、Person b){return a.age <b.age?-1:a.age == b.age?0:1; } Personは何も消費せず、年齢を生み出すように感じます。それは私を混乱させます。私の推論に欠陥はありますか、それともPECSはコレクションに対してのみ保持されますか?
Fatih Arslan、

24

簡単に言えば、PECSを覚えるための3つの簡単なルール:

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

10

この階層を想定してみましょう:

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>のタイプのオブジェクトは以下で追加することTList読み込めません。リストから以下のタイプのオブジェクトを読み取る
ために使用<? extends T>しますT要素を追加することはできません。


9

(ジェネリックのワイルドカードを使用した十分な例がないため、回答を追加します)

       // 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);
        }
    }

4

これは、私が拡張とスーパーを考える上で最も明確で最も簡単な方法です。

  • extends読書用です

  • super以下のためである書き込み

私は「PECS」は誰が「プロデューサー」で誰が「コンシューマー」であるかについて物事を考えるための自明ではない方法だと思います。「PECS」は、データコレクション自体の観点から定義されます。コレクションは、オブジェクトがそこに書き込まれいる場合は「消費」し(コードの呼び出しからオブジェクトを消費します)、オブジェクトがそこから読み取らている場合は「生成」します(それはいくつかの呼び出しコードに対してオブジェクトを生成しています)。これは、他のすべての名前の付け方に反しています。標準のJava APIは、コレクション自体ではなく、呼び出しコードの観点から名前が付けられています。たとえば、java.util.Listのコレクション中心のビューには、「add()」ではなく「receive()」という名前のメソッドが必要です–結局のところ、要素ですが、リスト自体要素を受け取ります。

コレクションとやり取りするコードの観点から物事を考える方が、より直感的で自然で一貫していると思います。コードはコレクションから「読み取り」または「書き込み」しますか?その後、コレクションに書き込むコードは「プロデューサー」になり、コレクションから読み取るコードは「コンシューマー」になります。


私は同じ精神的衝突に遭遇し、PECSがコードの名前を指定せず、型の境界自体コレクション宣言に設定されていることを除いて、同意する傾向があります。さらに、命名に関してはsrc、やのようなコレクションを作成/消費するための名前を持っていることがよくありますdst。つまり、コードとコンテナの両方を同時に扱っているのですが、私はそれらについて考えました。「コードを消費する」は生成中のコンテナから消費し、「コードを生成する」は消費するコンテナのために生成します。
mouselabs

4

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


2

これを覚えて:

消費者は夕食を食べます(スーパー)。プロデューサー両親の工場を拡張


1

実際の例を使用して(簡単化して):

  1. リストに例えると、貨車を積んだ貨物列車を想像してみてください。
  2. 貨物のサイズが貨物車と同じか小さい場合は、貨物車に貨物を入れることができます=<? super FreightCarSize>
  3. デポに十分な場所(貨物のサイズ以上)がある場合、貨物車から貨物を降ろすことができます=<? extends DepotSize>

1

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

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


0

例を見てみましょう

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);

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.