PECS คืออะไร (ผู้ผลิตขยาย Consumer Super)?


729

ฉันเจอ PECS (ย่อมาจากผู้ผลิตextendsและผู้บริโภคsuper ) ในขณะที่อ่านข้อมูลเกี่ยวกับยาชื่อสามัญ

มีใครอธิบายให้ฉันรู้วิธีใช้ PECS เพื่อแก้ไขความสับสนระหว่างextendsและsuper?


3
คำอธิบายที่ดีมากพร้อมด้วยตัวอย่าง @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 ซึ่งอธิบายsuperส่วนหนึ่ง แต่ให้ความคิดกับคนอื่น
lupchiazoem

คำตอบ:


843

tl; dr: "PECS" มาจากมุมมองของคอลเล็กชัน หากคุณกำลังเพียงดึงรายการจากคอลเลกชันทั่วไปก็เป็นผู้ผลิตและคุณควรใช้extends; ถ้าคุณกำลังเพียงsuperบรรจุในรายการก็เป็นผู้บริโภคและคุณควรใช้ ถ้าคุณทำทั้งที่มีคอลเลกชันเดียวกันคุณไม่ควรใช้อย่างใดอย่างหนึ่งหรือextendssuper


สมมติว่าคุณมีวิธีการที่จะใช้เวลาเป็นพารามิเตอร์ของคอลเลกชันของสิ่งที่ Collection<Thing>แต่คุณอยากให้มันเป็นความยืดหยุ่นมากขึ้นกว่าเพียงแค่การยอมรับ

กรณีที่ 1: คุณต้องการผ่านการรวบรวมและทำสิ่งต่าง ๆ กับแต่ละรายการ
แล้วรายการเป็นผู้ผลิตCollection<? extends Thing>ดังนั้นคุณจึงควรใช้

เหตุผลก็คือว่าCollection<? extends Thing>สามารถถือประเภทย่อยใด ๆThingและดังนั้นแต่ละองค์ประกอบจะทำหน้าที่เป็นThingเมื่อคุณดำเนินการของคุณ (จริง ๆ แล้วคุณไม่สามารถเพิ่มอะไรลงใน a Collection<? extends Thing>เนื่องจากคุณไม่สามารถรู้ได้ในขณะใช้งานจริงซึ่งมีประเภทย่อยเฉพาะของThingการเก็บสะสม)

กรณีที่ 2: คุณต้องการเพิ่มสิ่งต่าง ๆ ลงในคอลเลกชัน
แล้วรายการเป็นผู้บริโภคCollection<? super Thing>ดังนั้นคุณจึงควรใช้

เหตุผลที่นี่คือที่แตกต่างCollection<? extends Thing>กันCollection<? super Thing>สามารถถือThingไม่ว่าประเภทพารามิเตอร์ที่เกิดขึ้นจริงคืออะไร ที่นี่คุณไม่สนใจสิ่งที่มีอยู่แล้วในรายการตราบเท่าที่มันจะอนุญาตให้มีThingการเพิ่ม; นี่คือสิ่งที่? super Thingรับประกัน


142
ฉันมักจะพยายามที่จะคิดเกี่ยวกับมันด้วยวิธีนี้: การผลิตได้รับอนุญาตให้ผลิตบางสิ่งบางอย่างเฉพาะเจาะจงมากขึ้นจึงขยายเป็นผู้บริโภคที่ได้รับอนุญาตที่จะยอมรับสิ่งที่ทั่วไปมากขึ้นด้วยเหตุนี้ซุปเปอร์
Feuermurmel

10
อีกวิธีหนึ่งในการจดจำความแตกต่างของผู้ผลิต / ผู้บริโภคคือการคิดลายเซ็นวิธีการ หากคุณมีวิธีการที่doSomethingWithList(List list)คุณกำลังบริโภครายการและจะต้องมีความแปรปรวน / ขยาย (หรือรายการคงที่) ในทางตรงกันข้ามหากวิธีการของคุณคือList doSomethingProvidingListจากนั้นคุณจะผลิตรายการและจะต้อง contravariance / super (หรือรายการคงที่)
Raman

3
@MichaelMyers: ทำไมเราไม่สามารถใช้ประเภทพารามิเตอร์สำหรับทั้งสองกรณีนี้ได้ มีข้อได้เปรียบที่เฉพาะเจาะจงในการใช้ไวด์การ์ดที่นี่หรือเป็นเพียงวิธีการปรับปรุงความสามารถในการอ่านคล้ายกับพูดโดยใช้การอ้างอิงถึงconstพารามิเตอร์เมธอดใน C ++ เพื่อแสดงว่าวิธีนั้นไม่ได้แก้ไขข้อโต้แย้ง?
Chatterjee

7
@ รามันฉันคิดว่าคุณแค่สับสน ใน doSthWithList (คุณสามารถมี List <? super Thing>) เนื่องจากคุณเป็น Consumer คุณสามารถใช้ super (Remember, CS) อย่างไรก็ตามมันคือรายการ <? ขยายสิ่ง> getList () เนื่องจากคุณได้รับอนุญาตให้ส่งคืนสิ่งที่เฉพาะเจาะจงมากขึ้นเมื่อผลิต (PE)
masterxilo

4
@AZ_ ฉันแบ่งปันความรู้สึกของคุณ หากวิธีการได้รับ () จากรายการวิธีนั้นจะถือเป็น Consumer <T> และรายการนั้นจะถือเป็นผู้ให้บริการ แต่กฎของ PECS คือ“ จากมุมมองของรายการ” ดังนั้นจึงเรียกว่า 'การขยาย' ควรเป็น GEPS: รับการขยาย; ใส่สุด
Treefish Zhang

561

หลักการที่อยู่เบื้องหลังเรื่องนี้ในวิทยาการคอมพิวเตอร์เรียกว่า

  • ความแปรปรวนร่วม: ? extends MyClass,
  • ความแตกต่าง: ? super MyClassและ
  • แปรเปลี่ยน / ไม่แสวงหาแปรปรวน: MyClass

รูปภาพด้านล่างควรอธิบายแนวคิด รูปภาพมารยาท: Andrey Tyukin

ความแปรปรวนร่วมเทียบกับ


143
เฮ้ทุกคน. ฉัน Andrey Tyukin ฉันแค่ต้องการยืนยันว่า anoopelias & DaoWen ติดต่อฉันและได้รับอนุญาตจากฉันให้ใช้ร่างภาพร่างมันได้รับอนุญาตภายใต้ (CC) -BY-SA ขอบคุณ @ Anoop สำหรับการให้ชีวิตที่สอง ^^ @Brian Agnew: (ใน "คะแนนเสียงไม่กี่คน"): นั่นเป็นเพราะภาพร่างของ Scala มันใช้ไวยากรณ์ของ Scala และถือว่าความแปรปรวนของไซต์ประกาศซึ่งค่อนข้างแตกต่างจากการโทรแปลก ๆ ของ Java ความแปรปรวนในสถานที่ ... บางทีฉันควรจะเขียนคำตอบที่มีรายละเอียดมากขึ้นซึ่งแสดงให้เห็นอย่างชัดเจนว่าภาพร่างนี้ใช้กับ Java ได้อย่างไร ...
Andrey Tyukin

3
นี่เป็นหนึ่งในคำอธิบายที่ง่ายและชัดเจนที่สุดสำหรับความแปรปรวนร่วมและความแปรปรวนที่ฉันได้พบ!
cs4r

@Andrey Tyukin สวัสดีฉันก็ต้องการใช้ภาพนี้ ฉันจะติดต่อกับคุณได้อย่างไร?
slouc

หากคุณมีคำถามใด ๆ เกี่ยวกับภาพประกอบนี้เราสามารถพูดคุยพวกเขาได้ในห้องสนทนา: chat.stackoverflow.com/rooms/145734/…
Andrey Tyukin


48

PECS (ผู้ผลิตextendsและผู้บริโภคsuper)

mnemonic →รับและใส่หลักการ

หลักการนี้ระบุว่า:

  • ใช้สัญลักษณ์แทนการขยายเมื่อคุณรับค่าจากโครงสร้างเท่านั้น
  • ใช้สัญลักษณ์พิเศษเมื่อคุณใส่ค่าลงในโครงสร้าง
  • และอย่าใช้สัญลักษณ์แทนเมื่อคุณทั้งคู่ได้รับและวาง

ตัวอย่างใน 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

ภายในระบบการพิมพ์ของภาษาการเขียนโปรแกรมกฎการพิมพ์

  • covariantถ้ามันรักษาลำดับของประเภท (≤), ซึ่งคำสั่งประเภทจากที่เฉพาะเจาะจงมากขึ้นเพื่อสามัญมากขึ้น;
  • contravariantถ้ามันฝืนการสั่งซื้อนี้
  • ไม่แปรผันหรือไม่แปรเปลี่ยนหากไม่ได้นำมาใช้

ความแปรปรวนร่วมและความแปรปรวน

  • อ่านอย่างเดียวชนิดข้อมูล (แหล่งที่มา) สามารถcovariant ;
  • เขียนเพียงประเภทข้อมูล (อ่าง) สามารถcontravariant
  • ชนิดข้อมูลที่ไม่แน่นอนซึ่งทำหน้าที่เป็นทั้งแหล่งที่มาและอ่างล้างมือควรจะคงที่

เพื่อแสดงปรากฏการณ์ทั่วไปนี้ให้พิจารณาประเภทของอาร์เรย์ สำหรับประเภทสัตว์เราสามารถสร้างประเภทสัตว์ []

  • covariant : แมว [] เป็นสัตว์ [];
  • contravariant : สัตว์ [] เป็นแมว [];
  • ไม่เปลี่ยนแปลง : สัตว์ [] ไม่ใช่แมว [] และแมว [] ไม่ใช่สัตว์ []

ตัวอย่าง 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 Wildcards ที่แตกต่างกัน:

  • ในความแปรปรวน / ไม่แปรปรวน: ?หรือ? extends Object- ไวด์การ์ดที่ไม่ได้ จำกัด มันหมายถึงครอบครัวทุกประเภท ใช้เมื่อคุณทั้งคู่ได้รับและวาง
  • ร่วมแปรปรวน: ? extends T(ครอบครัวทุกประเภทที่มีชนิดย่อยของ T) - สัญลักษณ์แทนกับขอบเขตบน Tเป็นชั้นบนสุดในลำดับชั้นการสืบทอด ใช้extendsสัญลักษณ์แทนเมื่อคุณจะได้รับค่าจากโครงสร้าง
  • Contra-แปรปรวน: ? super T(ครอบครัวทุกประเภทที่มี supertypes ของ T) - สัญลักษณ์แทนด้วยเป็นขอบเขตล่าง Tเป็นที่ต่ำกว่าระดับ -most ในลำดับชั้นมรดก ใช้superสัญลักษณ์แทนเมื่อคุณใส่ค่าลงในโครงสร้าง

หมายเหตุ: สัญลักษณ์ตัวแทน?หมายถึงศูนย์หรือหนึ่งครั้งแสดงประเภทที่ไม่รู้จัก wildcard สามารถใช้เป็นชนิดของพารามิเตอร์, ไม่เคยใช้เป็นอาร์กิวเมนต์ชนิดสำหรับการเรียกใช้เมธอดทั่วไป, การสร้างอินสแตนซ์คลาสทั่วไป (เช่นเมื่อใช้ไวด์การ์ดที่อ้างอิงไม่ได้ใช้ในที่อื่น ๆ ในโปรแกรมเหมือนที่เราใช้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.
        */  
    }
}

ข้อมูลทั่วไปและตัวอย่าง


เฮ้ฉันแค่อยากจะรู้ว่าคุณหมายถึงอะไรกับประโยคสุดท้าย: "ถ้าคุณคิดว่าการเปรียบเทียบของฉันผิดโปรดอัปเดต" คุณหมายถึงถ้ามันผิดจริยธรรม (ซึ่งเป็นอัตนัย) หรือถ้ามันผิดในบริบทของการเขียนโปรแกรม (ซึ่งมีวัตถุประสงค์: ไม่มันไม่ผิด) ฉันต้องการแทนที่ด้วยตัวอย่างที่เป็นกลางกว่าซึ่งเป็นที่ยอมรับในระดับสากลโดยไม่ขึ้นกับบรรทัดฐานทางวัฒนธรรมและความเชื่อทางจริยธรรม ถ้าเป็นเช่นนั้นกับคุณ
Neuron

ในที่สุดฉันจะได้รับมัน คำอธิบายที่ดี
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.ฉันไม่สามารถเพิ่มองค์ประกอบลงในรายการ <?> หรือรายการ <? ขยาย Object> ดังนั้นฉันไม่เข้าใจว่าทำไมจึงเป็นUse when you both get and putเช่นนั้น
LiuWenbin_NO

1
@LiuWenbin_NO - ส่วนหนึ่งของคำตอบนั้นทำให้เข้าใจผิด ?- "wildcard ที่ไม่ได้ จำกัด " - สอดคล้องกับสิ่งที่ตรงกันข้ามแน่นอน โปรดดูเอกสารประกอบต่อไปนี้: docs.oracle.com/javase/tutorial/java/generics/ ......ซึ่งระบุ: ในกรณีที่รหัสต้องการเข้าถึงตัวแปรเป็นทั้งตัวแปร "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
    }
}

ดังนั้น "? ขยาย B" ควรตีความว่า "? B ขยาย" เป็นสิ่งที่ B ขยายออกเพื่อที่จะรวมคลาส super ทั้งหมดของ B จนถึง Object ไม่รวม B เอง ขอบคุณสำหรับรหัส!
Saurabh Patil

3
@SaurabhPatil ไม่? extends Bหมายถึง B และทุกอย่างที่ขยาย B
asgs

24

ขณะที่ผมอธิบายในคำตอบของฉันคำถามอื่นเต้าเป็นอุปกรณ์ช่วยในการจำที่สร้างขึ้นโดยจอชโบลชช่วยจำP roducer extends, Csuper onsumer

ซึ่งหมายความว่าเมื่อมีการแปรชนิดจะถูกส่งผ่านไปยังวิธีการจะผลิตกรณีของT(พวกเขาจะถูกดึงจากมันในทางใดทางหนึ่ง) ? extends Tควรจะใช้ตั้งแต่ตัวอย่างของ subclass ของใด ๆนอกจากนี้ยังมีTT

เมื่อชนิดแปรถูกส่งผ่านไปยังวิธีการที่จะใช้กรณีของT(พวกเขาจะถูกส่งผ่านไปเพื่อทำอะไรบางอย่าง) ? super Tควรจะนำมาใช้เพราะเป็นตัวอย่างของTถูกต้องตามกฎหมายได้รับการส่งผ่านไปยังวิธีการใด ๆ ที่ยอมรับ supertype Tบาง Comparator<Number>สามารถนำมาใช้ในCollection<Integer>ตัวอย่างเช่น ? extends Tจะไม่ทำงานเพราะไม่สามารถทำงานบนComparator<Integer>Collection<Number>

โปรดทราบว่าโดยทั่วไปคุณควรใช้? extends Tและ? super Tพารามิเตอร์ของวิธีการบางอย่างเท่านั้น เมธอดควรใช้Tเป็นพารามิเตอร์ type บนประเภท return ทั่วไป


1
หลักการนี้มีไว้สำหรับ Collections เท่านั้นหรือไม่ มันสมเหตุสมผลเมื่อพยายามจะสัมพันธ์กับรายการ ถ้าคุณคิดเกี่ยวกับลายเซ็นของการเรียงลำดับ (รายการ <T>, Comparator <? super T>) ---> ที่นี่ Comparator ใช้ super ดังนั้นจึงหมายความว่าผู้บริโภคในบริบท PECS เมื่อคุณดูการใช้งานเช่น: การเปรียบเทียบ int สาธารณะ (บุคคล a, บุคคล b) {ส่งคืน a.age <b.age? -1: a.age == b.age? 0: 1; } ฉันรู้สึกว่า Person ไม่ได้กินอะไรเลยนอกจากสร้างอายุ นั่นทำให้ฉันสับสน มีเหตุผลในการให้เหตุผลของฉันหรือ PECS เพียงเพื่อสะสมเท่านั้น?
Fatih Arslan

23

สรุปกฎง่ายๆสามข้อในการจดจำ PECS:

  1. ใช้<? extends T>wildcard หากคุณต้องการดึงอ็อบเจกต์ประเภท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 - ผู้ผลิตขยาย:

List<? extends Shark> sharks = new ArrayList<>();

ทำไมคุณไม่สามารถเพิ่มวัตถุที่ขยาย "ฉลาม" ในรายการนี้ ชอบ:

sharks.add(new HammerShark());//will result in compilation error

เนื่องจากคุณมีรายการที่สามารถเป็นประเภท A, B หรือ C ที่รันไทม์คุณจึงไม่สามารถเพิ่มวัตถุประเภท A, B หรือ C ในรายการเนื่องจากคุณสามารถท้ายด้วยชุดค่าผสมที่ไม่ได้รับอนุญาตใน java
ในทางปฏิบัติผู้แปลสามารถเห็น compiletime ที่คุณเพิ่ม B:

sharks.add(new HammerShark());

... แต่มันไม่มีวิธีที่จะบอกได้ว่าตอนรันไทม์ B ของคุณจะเป็นประเภทย่อยหรือ supertype ของชนิดรายการ ที่รันไทม์ชนิดรายการสามารถเป็นประเภท A, B, C ดังนั้นคุณจึงไม่สามารถเพิ่ม HammerSkark (super type) ในรายการ DeadHammerShark ได้

* คุณจะพูดว่า: "ตกลง แต่ทำไมฉันไม่สามารถเพิ่ม HammerSkark ในประเภทนี้เพราะมันเป็นประเภทที่เล็กที่สุด" คำตอบ: มันเล็กที่สุดที่คุณรู้ แต่ HammerSkark สามารถขยายได้โดยบุคคลอื่นเช่นกันและคุณจะจบลงในสถานการณ์เดียวกัน

ขอชี้แจง CS - Consumer Super:

ในลำดับชั้นเดียวกันเราสามารถลอง:

List<? super Shark> sharks = new ArrayList<>();

คุณสามารถเพิ่มอะไรลงในรายการนี้และทำไม

sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());

คุณสามารถเพิ่มประเภทของวัตถุด้านบนได้เนื่องจากสิ่งใดก็ตามที่อยู่ด้านล่างฉลาม (A, B, C) จะเป็นประเภทย่อยของสิ่งที่อยู่เหนือฉลาม (X, Y, Z) เสมอ เข้าใจง่าย

คุณไม่สามารถเพิ่มชนิดด้านบน Shark ได้เนื่องจากในขณะใช้งานจริงชนิดของวัตถุที่เพิ่มอาจมีลำดับชั้นได้สูงกว่าชนิดที่ประกาศของรายการ (X, Y, Z) สิ่งนี้ไม่ได้รับอนุญาต

แต่ทำไมคุณไม่สามารถอ่านจากรายการนี้ (ฉันหมายความว่าคุณสามารถเอาองค์ประกอบออกมาได้ แต่คุณไม่สามารถกำหนดให้กับสิ่งอื่นนอกเหนือจาก Object 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 (สัตว์) อาจต่ำกว่า ลำดับชั้นกว่าประเภทที่ประกาศของรายการ (ซึ่งอาจเป็นสิ่งมีชีวิตหรือสูงกว่า) สิ่งนี้ไม่ได้รับอนุญาต

เพื่อสรุป

เราใช้<? super T>เพื่อเพิ่มวัตถุประเภทเท่ากับหรือต่ำไปT Listเราอ่านไม่ออก
เราใช้<? extends T>เพื่ออ่านวัตถุประเภทเท่ากันหรือด้านล่างTจากรายการ เราไม่สามารถเพิ่มองค์ประกอบเข้าไปได้


9

(การเพิ่มคำตอบเพราะมีตัวอย่างไม่มากพอที่จะใช้ wildcard แทน)

       // 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ควรมีวิธีชื่อ "receive ()" แทน "add ()" - หลังจากทั้งหมดองค์ประกอบ แต่รายการตัวเองได้รับองค์ประกอบ

ฉันคิดว่ามันใช้งานง่ายกว่าเป็นธรรมชาติและสอดคล้องกับการคิดสิ่งต่าง ๆ จากมุมมองของรหัสที่โต้ตอบกับคอลเลกชัน - รหัส "อ่านจาก" หรือ "เขียนถึง" คอลเลกชันหรือไม่ ต่อจากนั้นการเขียนโค้ดใด ๆไปยังคอลเลกชันจะเป็น "ผู้สร้าง" และการอ่านโค้ดใด ๆจากคอลเลกชันจะเป็น "ผู้บริโภค"


ฉันพบว่ามีการชนกันของจิตใจเดียวกันและมีแนวโน้มที่จะเห็นด้วยยกเว้นว่า PECS ไม่ได้ระบุการตั้งชื่อรหัสและขอบเขตของประเภทตัวเองถูกตั้งค่าในการประกาศการรวบรวม นอกจากนี้เท่าที่ตั้งชื่อเป็นห่วงคุณมักจะมีชื่อในการผลิต / การบริโภคคอลเลกชันเหมือนและsrc dstดังนั้นคุณกำลังจัดการกับทั้งรหัสและคอนเทนเนอร์ในเวลาเดียวกันและฉันก็คิดเกี่ยวกับมันตามบรรทัดเหล่านั้น - "การใช้รหัส" บริโภคจากภาชนะที่ผลิตและ "ผลิตรหัส" ผลิตสำหรับภาชนะที่บริโภค
mouselabs

4

"กฎ" PECS เพียงทำให้แน่ใจว่าสิ่งต่อไปนี้ถูกกฎหมาย:

  • ผู้บริโภค: อะไรก็ตาม?ก็สามารถอ้างถึงได้อย่างถูกกฎหมาย T
  • โปรดิวเซอร์: ไม่ว่า?จะเป็นอะไรก็สามารถอ้างถึงได้โดยชอบด้วยกฎหมาย T

การจับคู่ตามปกติList<? extends T> producer, List<? super T> consumerจะช่วยให้มั่นใจได้ว่าคอมไพเลอร์สามารถบังคับใช้กฎความสัมพันธ์การสืบทอดมาตรฐาน "IS-A" ถ้าเราสามารถทำเช่นนั้นถูกต้องตามกฎหมายก็อาจจะง่ายที่จะพูด<T extends ?>, <? extends T>(หรือดีกว่ายังอยู่ในสกาล่าที่คุณสามารถดูด้านบนมัน[-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);
}

แสดงนี้ในแง่ของการเปรียบเทียบที่ได้รับมอบหมายสำหรับตัวแทน (ไม่ทราบชนิด) จะอ้างอิง - ที่ "ด้านซ้ายมือ" ของงาน - และเพื่อให้แน่ใจว่าสิ่งที่เป็น"IS-A" - ที่สามารถกำหนดให้กับมันเพราะเป็นชนิดสุด (หรืออย่างมากที่สุดชนิดเดียวกัน) เช่นconsumer?<? super T>?T?T?T

สำหรับproducerความกังวลเหมือนกันมันคว่ำเพียง: producer's ?ตัวแทน (ไม่ทราบชนิด) เป็นอ้างอิง - ที่ 'ด้านขวามือ' ของงาน - และ<? extends T>เพื่อให้แน่ใจว่าสิ่งที่?เป็น?'IS-A' T- ว่ามันจะได้รับมอบหมายให้เป็นTเพราะ?เป็นชนิดย่อย (หรืออย่างน้อยประเภทเดียวกัน) Tเช่น


2

จำสิ่งนี้ไว้:

ผู้บริโภคกินอาหารมื้อเย็น (super); ผู้ผลิตขยายโรงงานของพ่อแม่


1

ใช้ตัวอย่างชีวิตจริง (มีการทำให้เข้าใจง่าย):

  1. ลองนึกภาพขบวนรถไฟบรรทุกสินค้าที่มีรถบรรทุกคล้ายกับรายการ
  2. คุณสามารถวางสินค้าไว้ในรถขนส่งได้หากสินค้ามีขนาดเท่ากันหรือเล็กกว่ารถบรรทุก /<? super FreightCarSize>
  3. คุณสามารถขนถ่ายสินค้าจากรถขนส่งสินค้าได้ถ้าคุณมีที่เพียงพอ (มากกว่าขนาดของสินค้า) ในคลังของคุณ =<? extends DepotSize>

1

แปรปรวน : ยอมรับเชื้อ
contravariance : ยอมรับ supertypes

ประเภท Covariant เป็นแบบอ่านอย่างเดียวในขณะที่ประเภทที่แปรปรวนเป็นแบบเขียนอย่างเดียว


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>aka ความแปรปรวนร่วมบนอาคาผู้ผลิต aka รับประกันความถูกต้องว่าดำเนินการโดย 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.