สามารถเพิ่มองค์ประกอบลงในคอลเลกชันทั่วไปของไวด์การ์ดได้อย่างไร

เหตุใดฉันจึงได้รับข้อผิดพลาดของคอมไพเลอร์ด้วยโค้ด Java นี้

1  public List<? extends Foo> getFoos()
2  {
3    List<? extends Foo> foos = new ArrayList<? extends Foo>();
4    foos.add(new SubFoo());
5    return foos;
6  }

โดยที่ 'SubFoo' เป็นคลาสที่เป็นรูปธรรมที่ใช้ Foo และ Foo เป็นอินเทอร์เฟซ

ข้อผิดพลาดที่ฉันได้รับจากรหัสนี้:

  • ออนไลน์ 3: "ไม่สามารถยกตัวอย่าง ArrayList‹ ได้ขยาย Foo›"
  • ออนไลน์ 4: "วิธีการ add(capture#1-of ? extends Foo) ในประเภท List‹capture#1-of ? extends Foo› ไม่สามารถใช้ได้กับอาร์กิวเมนต์ (SubFoo)"

อัปเดต: ขอบคุณ Jeff C ฉันสามารถเปลี่ยนบรรทัด 3 เป็นพูดว่า "new ArrayList‹Foo›();" แต่ฉันยังคงมีปัญหากับสาย 4


person David Koelle    schedule 06.10.2008    source แหล่งที่มา


คำตอบ (5)


ใช้สิ่งนี้แทน:

1  public List<? extends Foo> getFoos()
2  {
3    List<Foo> foos = new ArrayList<Foo>(); /* Or List<SubFoo> */
4    foos.add(new SubFoo());
5    return foos;
6  }

เมื่อคุณประกาศ foos เป็น List<? extends Foo> คอมไพลเลอร์จะไม่รู้ว่าการเพิ่ม SubFoo นั้นปลอดภัย จะเกิดอะไรขึ้นถ้า ArrayList<AltFoo> ถูกกำหนดให้กับ foos? นั่นจะเป็นการมอบหมายที่ถูกต้อง แต่การเพิ่ม SubFoo จะทำให้คอลเลกชันเสียหาย

person erickson    schedule 06.10.2008

แค่คิดว่าฉันจะเพิ่มเธรดเก่านี้โดยสรุปคุณสมบัติของพารามิเตอร์รายการที่สร้างอินสแตนซ์ด้วยประเภทหรือไวด์การ์ด....

เมื่อเมธอดมีพารามิเตอร์/ผลลัพธ์ที่เป็นรายการ การใช้ประเภทอินสแตนซ์หรือไวด์การ์ดจะกำหนด

  1. ประเภทของรายการที่สามารถส่งผ่านไปยังเมธอดเป็นอาร์กิวเมนต์ได้
  2. ประเภทของรายการที่สามารถเติมได้จากผลลัพธ์ของวิธีการ
  3. ประเภทขององค์ประกอบที่สามารถเขียนลงในรายการภายในเมธอดได้
  4. ประเภทที่สามารถเติมได้เมื่ออ่านองค์ประกอบจากรายการภายในเมธอด

พารามิเตอร์/ประเภทการส่งคืน: List< Foo>

  1. Types of List which can be passed to the method as an argument:
    • List< Foo>
  2. Types of List which can be populated from the method result:
    • List< Foo>
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
  3. Types of elements which can be written to list within the method:
    • Foo & subtypes
  4. Types which can be populated when reading elements from list within the method:
    • Foo & supertypes (up to Object)

พารามิเตอร์/ประเภทการส่งคืน: List< ? extends Foo>

  1. Types of List which can be passed to the method as an argument:
    • List< Foo>
    • List< Subfoo>
    • List< SubSubFoo>
    • List< ? extends Foo>
    • List< ? extends SubFoo>
    • List< ? extends SubSubFoo>
  2. Types of List which can be populated from the method result:
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
    • List< ? extends SuperSuperFoo>
  3. Types of elements which can be written to list within the method:
    • None! Not possible to add.
  4. Types which can be populated when reading elements from list within the method:
    • Foo & supertypes (up to Object)

พารามิเตอร์/ประเภทการส่งคืน: List<? super Foo>

  1. Types of List which can be passed to the method as an argument:
    • List< Foo>
    • List< Superfoo>
    • List< SuperSuperFoo>
    • List< ? super Foo>
    • List< ? super SuperFoo>
    • List< ? super SuperSuperFoo>
  2. Types of List which can be populated from the method result:
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? super SubSubFoo>
  3. Types of elements which can be written to list within the method:
    • Foo & supertypes
  4. Types which can be populated when reading elements from list within the method:
    • Foo & supertypes (up to Object)

การตีความ/ความคิดเห็น

  • ความต้องการของผู้โทรภายนอกขับเคลื่อนการออกแบบการประกาศวิธีการ เช่น API สาธารณะ (โดยปกติจะเป็นการพิจารณาเบื้องต้น)
  • ความต้องการของตรรกะวิธีการภายในขับเคลื่อนการตัดสินใจเพิ่มเติมเกี่ยวกับประเภทข้อมูลจริงที่ประกาศและสร้างภายใน (โดยปกติจะเป็นการพิจารณารอง)
  • ใช้ List<Foo> หากรหัสผู้โทรมุ่งเน้นไปที่การจัดการคลาส Foo เสมอ เนื่องจากจะเพิ่มความยืดหยุ่นสูงสุดสำหรับทั้งการอ่านและเขียน
  • ใช้ List<? extends UpperMostFoo> หากอาจมีผู้โทรหลายประเภท โดยเน้นไปที่การจัดการคลาสอื่น (ไม่ใช่ Foo เสมอไป) และมีคลาสบนสุดเพียงคลาสเดียวในลำดับชั้นประเภท Foo และหากวิธีการคือการเขียนภายในรายการและรายชื่อผู้โทร การจัดการกำลังอ่าน ที่นี่วิธีการอาจใช้ List< UpperMostFoo> ภายในและเพิ่มองค์ประกอบเข้าไปก่อนที่จะส่งคืน List< ? extends UpperMostFoo>
  • หากอาจมีผู้เรียกหลายประเภท โดยเน้นที่การจัดการคลาสอื่น (ไม่ใช่ Foo เสมอไป) และหากจำเป็นต้องอ่านและเขียนลงในรายการ และมีคลาสต่ำสุดเพียงคลาสเดียวในลำดับชั้นประเภท Foo ก็สมเหตุสมผลที่จะใช้ List< ? super LowerMostFoo>
person Glen Best    schedule 16.07.2012
comment
ฉันคิดว่า 3 และ 4 สำหรับ List<? super Foo> ผิด ควรเป็น Foo & ชนิดย่อยและ Object ตามลำดับ - person FuegoFro; 22.08.2016

พยายาม:

public List<Foo> getFoos() {
    List<Foo> foos = new ArrayList<Foo>();
    foos.add(new SubFoo());
    return foos;
}

ตัวสร้าง ArrayList ทั่วไปจำเป็นต้องมีประเภทเฉพาะเพื่อใช้กำหนดพารามิเตอร์ คุณไม่สามารถใช้เครื่องหมาย '?' ไวด์การ์ดที่นั่น การเปลี่ยนอินสแตนซ์เป็น "new ArrayList‹Foo›()' จะช่วยแก้ไขข้อผิดพลาดในการคอมไพล์ครั้งแรก

การประกาศตัวแปร 'foos' สามารถมีไวด์การ์ดได้ แต่เนื่องจากคุณทราบประเภทที่แน่นอน จึงเหมาะสมกว่าที่จะอ้างอิงข้อมูลประเภทเดียวกันที่นั่น สิ่งที่คุณพูดตอนนี้ว่า foos มีประเภทย่อยเฉพาะของ Foo แต่เราไม่รู้ว่าประเภทใด อาจไม่อนุญาตให้เพิ่ม SubFoo เนื่องจาก SubFoo ไม่ใช่ "ประเภทย่อยทั้งหมดของ Foo" การเปลี่ยนการประกาศเป็น 'List‹Foo› foos = ' จะช่วยแก้ไขข้อผิดพลาดในการคอมไพล์ครั้งที่สอง

สุดท้ายนี้ ฉันจะเปลี่ยนประเภทการส่งคืนเป็น 'List‹Foo›' เนื่องจากไคลเอนต์ของวิธีนี้จะไม่สามารถทำอะไรได้มากกับค่าที่ส่งคืนตามที่กำหนดไว้ในปัจจุบัน คุณไม่ควรใช้ไวด์การ์ดในประเภทการส่งคืน ใช้ลายเซ็นวิธีการแบบกำหนดพารามิเตอร์หากจำเป็น แต่ต้องการให้ประเภทที่มีขอบเขตปรากฏเฉพาะในอาร์กิวเมนต์ของวิธีการเท่านั้น เนื่องจากนั่นขึ้นอยู่กับผู้เรียกที่สามารถส่งผ่านประเภทเฉพาะและดำเนินการตามลำดับ

person Community    schedule 06.10.2008
comment
ฉันไม่เห็นด้วยกับประเด็นสุดท้ายของคุณ ประเภทการส่งคืนไวด์การ์ดมีประโยชน์มากหากผู้โทรต้องการเอาสิ่งต่าง ๆ ออกจากรายการ - person newacct; 16.07.2012

สิ่งต่อไปนี้จะทำงานได้ดี:

public List<? extends Foo> getFoos() {
    List<Foo> foos = new ArrayList<Foo>();
    foos.add(new SubFoo());
    return foos;
}
person SCdF    schedule 06.10.2008

หากต้องการทราบว่ายาชื่อสามัญทำงานอย่างไร โปรดดูตัวอย่างนี้:

    List<SubFoo> sfoo = new ArrayList<SubFoo>();
    List<Foo> foo;
    List<? extends Foo> tmp;

    tmp = sfoo;
    foo = (List<Foo>) tmp;

ประเด็นก็คือ นั่นไม่ได้ออกแบบมาสำหรับตัวแปรโลคัล/สมาชิก แต่สำหรับลายเซ็นฟังก์ชัน นั่นเป็นเหตุผลว่าทำไมมันถึงถอยหลังมาก

person zslevi    schedule 23.03.2011