Java Generics (สัญลักษณ์แทน)


110

ฉันมีคำถามสองสามข้อเกี่ยวกับไวด์การ์ดทั่วไปใน Java:

  1. อะไรคือความแตกต่างระหว่างList<? extends T>และList<? super T>?

  2. wildcard ที่มีขอบเขตคืออะไรและ wildcard ที่ไม่มีขอบเขตคืออะไร?


ในกรณีที่คำตอบของ Ted Gao ถูกลบ (เนื่องจากเป็นแบบลิงก์เท่านั้น) นี่คือบล็อกโพสต์ที่ลิงก์ไป
royhowie

คำตอบ:


123

ในคำถามแรกของคุณ<? extends T>และ<? super T>เป็นตัวอย่างของสัญลักษณ์แทนแบบมีขอบเขต มีลักษณะสัญลักษณ์แทนมากมายเช่นและโดยทั่วไปหมายถึง<?> <? extends Object>หมายความว่าทั่วไปอย่างหลวม ๆ อาจเป็นประเภทใดก็ได้ สัญลักษณ์แทนแบบมีขอบเขต ( <? extends T>หรือ<? super T>) กำหนดข้อ จำกัด เกี่ยวกับประเภทโดยบอกว่าต้องขยายประเภทเฉพาะ ( <? extends T>เรียกว่าขอบเขตบน) หรือต้องเป็นบรรพบุรุษของประเภทที่เฉพาะเจาะจง ( <? super T>เรียกว่าขอบเขตล่าง) .

ชวาสอนมีบางคำอธิบายที่ดีงามของยาชื่อสามัญในบทความตัวแทนและสนุกยิ่งขึ้นกับตัวแทน


เพื่อให้ถูกต้องถ้า A <B และ B <C แล้ว: <A ขยาย C> ผิด?
Pablo Fernandez

ถ้าโดย A <B คุณหมายถึง A ขยาย B ดังนั้น A จะขยาย C คุณจะไม่ใช้สิ่งนั้นในไวยากรณ์ตัวแทนคุณจะพูดว่า <? ขยาย C> เพื่อ จำกัด ตัวเลือกของคุณเป็น A หรือ B
Bill the Lizard

และในกรณีนั้นถ้าฉันพูดว่า <? super C> อะไรคือความแตกต่าง?
Pablo Fernandez

3
แค่อยากจะแนะนำข้อมูลอ้างอิงอื่นเกี่ยวกับ Java Generics: angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
Zach Scrivena

3
@Pablo: <? super C>หมายความว่าประเภทของคุณถูก จำกัด ไว้เฉพาะบางอย่างข้างต้นCในลำดับชั้นประเภท (ขออภัยสำหรับการตอบกลับช้ามากฉันเดาว่าเราไม่มีการแจ้งเตือนความคิดเห็นเมื่อ 2 ปีก่อน)
Bill the Lizard

49

หากคุณมีลำดับชั้นของคลาส A, B คือคลาสย่อยของ A และ C และ D ทั้งสองเป็นคลาสย่อยของ B เช่นด้านล่าง

class A {}
class B extends A {}
class C extends B {}
class D extends B {}

แล้ว

List<? extends A> la;
la = new ArrayList<B>();
la = new ArrayList<C>();
la = new ArrayList<D>();

List<? super B> lb;
lb = new ArrayList<A>(); //fine
lb = new ArrayList<C>(); //will not compile

public void someMethod(List<? extends B> lb) {
    B b = lb.get(0); // is fine
    lb.add(new C()); //will not compile as we do not know the type of the list, only that it is bounded above by B
}

public void otherMethod(List<? super B> lb) {
    B b = lb.get(0); // will not compile as we do not know whether the list is of type B, it may be a List<A> and only contain instances of A
    lb.add(new B()); // is fine, as we know that it will be a super type of A 
}

สัญลักษณ์แทนขอบเขตก็เหมือนกับ? extends Bที่ B เป็นบางประเภท นั่นคือไม่ทราบประเภท แต่สามารถวาง "ผูก" ไว้ได้ ในกรณีนี้มันถูกล้อมรอบด้วยคลาสบางคลาสซึ่งเป็นคลาสย่อยของ B


ฉันถือว่าList<? super B>อธิบายว่ารายการยอมรับประเภทที่เป็นคลาสแม่ของคลาส Bหรือไม่ ซึ่งเป็นสาเหตุที่ C และ D ไม่รวบรวมหืม?
Volkan Güven

38

Josh Bloch ยังมีคำอธิบายที่ดีว่าควรใช้เมื่อใดsuperและextendsในวิดีโอพูดคุยของ google ioนี้ซึ่งเขากล่าวถึงผู้ช่วยจำProducer extendsConsumersuper

จากสไลด์นำเสนอ:

สมมติว่าคุณต้องการเพิ่มวิธีการจำนวนมากให้ Stack<E>

void pushAll(Collection<? extends E> src);

- src เป็นผู้ผลิต E

void popAll(Collection<? super E> dst);

- dst เป็นผู้บริโภค E


ฉันได้อ่านหนังสือของ Bloch แต่ฉันยังไม่เห็นความแตกต่างระหว่างการขยายและขั้นสุดในกรณีนี้
Pablo Fernandez

ดูวิดีโอฉันคิดว่ามันค่อนข้างชัดเจน นอกจากนี้ฉันคิดว่าคุณควรถามคำถามอื่นในเรื่องนี้ "อะไรคือความแตกต่างระหว่าง List <? expands T> และ List <? super T>" ซึ่งคุณหวังว่าจะได้รับคำตอบเพิ่มเติม (ถ้าคุณเป็นเช่นนั้นให้เพิ่มลิงค์จากที่นี่)
ว่างเปล่า

2
เตียง - ทำไมไม่รวมตัวอย่างไว้ในคำตอบนี้เพื่อให้สมบูรณ์และยืนอยู่คนเดียว ลิงค์เป็นเพียงชั่วคราว
James Schek

3

อาจมีบางครั้งที่คุณต้องการ จำกัด ประเภทของประเภทที่อนุญาตให้ส่งผ่านไปยังพารามิเตอร์ type ตัวอย่างเช่นวิธีการที่ดำเนินการกับตัวเลขอาจต้องการยอมรับเฉพาะอินสแตนซ์ของ Number หรือคลาสย่อยเท่านั้น นี่คือพารามิเตอร์ชนิดที่มีขอบเขต

Collection<? extends MyObject> 

หมายความว่าสามารถยอมรับวัตถุทั้งหมดที่มีความสัมพันธ์IS- Aกับ MyObject (เช่นวัตถุใด ๆ ที่เป็นประเภทของ myObject หรือเราสามารถพูดว่าวัตถุใด ๆ ของคลาสย่อยของ MyObject) หรือวัตถุของคลาส MyObject

ตัวอย่างเช่น:

class MyObject {}

class YourObject extends MyObject{}

class OurObject extends MyObject{}

จากนั้น

Collection<? extends MyObject> myObject; 

จะยอมรับเฉพาะ MyObject หรือลูก ๆ ของ MyObject (เช่นออบเจ็กต์ประเภท OurObject หรือ YourObject หรือ MyObject ใด ๆ แต่ไม่ใช่วัตถุระดับสูงของ MyObject)


1

โดยทั่วไปแล้ว

หากโครงสร้างมีองค์ประกอบที่มีประเภทของรูปแบบ? extends Eเราสามารถดึงองค์ประกอบออกจากโครงสร้างได้ แต่เราไม่สามารถใส่องค์ประกอบลงในโครงสร้างได้

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile-time error
assert ints.toString().equals("[1, 2, 3.14]"); 

ที่จะนำองค์ประกอบในโครงสร้างที่เราต้องการชนิดของสัญลักษณ์แทนอื่นที่เรียกว่าWildcards with super,

 List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
    List<Integer> ints = Arrays.asList(5, 6);
    Collections.copy(objs, ints);
    assert objs.toString().equals("[5, 6, four]");

    public static <T> void copy(List<? super T> dst, List<? extends T> src) {
          for (int i = 0; i < src.size(); i++) {
                dst.set(i, src.get(i));
         }
    }

1

ไวลด์การ์ดทั่วไปถูกสร้างขึ้นเพื่อทำให้เมธอดที่ทำงานบนคอลเล็กชันสามารถใช้ซ้ำได้มากขึ้น

ตัวอย่างเช่นหากเมธอดมีพารามิเตอร์List<A>เราสามารถให้เฉพาะList<A>เมธอดนี้ มันเป็นการสูญเปล่าสำหรับการใช้วิธีนี้ในบางสถานการณ์:

  1. หากวิธีนี้อ่านเฉพาะวัตถุจากList<A>นั้นเราควรได้รับอนุญาตให้List<A-sub>ใช้วิธีนี้ (เพราะ A-sub คือ A)
  2. หากวิธีนี้แทรกวัตถุเข้าไปList<A>เท่านั้นเราควรได้รับอนุญาตให้List<A-super>ใช้วิธีนี้ (เพราะ A คือ A-super)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.