Java Generics (ไวด์การ์ดที่มีขอบเขต)

ตามหนังสือ "Effective Java" ของ Joshua Bloch มีกฎเกี่ยวกับวิธีการ/เมื่อใช้ wildcards ที่มีขอบเขตในยาชื่อสามัญ กฎนี้คือ PECS (Producer-Extends, Comsumer-Super) เมื่อฉันศึกษาตัวอย่างต่อไปนี้:

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

ฉันเข้าใจว่ากฎนี้เหมาะสมอย่างยิ่งในตัวอย่างนี้ ฉันต้องประกาศเมธอด pushAll เป็นตัวอย่างต่อไปนี้:

// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
    {
       push(e);
    }  
}

แต่จะเกิดอะไรขึ้นถ้าฉันมีตัวอย่างต่อไปนี้

Stack<Integer> integerStack = new Stack<Integer>();
Iterable<Number> numbers = ... ;
integerStack.pushAll(numbers);

ฉันต้องประกาศ pushAll ดังนี้:

public void pushAll(Iterable<? super E> src) {
    for (E e : src)
    {  
        push(e);
    }
}

ตามกฎของ PECS การประกาศข้างต้นไม่ถูกต้อง แต่ฉันอยากได้ Stack จาก Integers และส่งต่อไปยัง Stack a Number นี้ ทำไมไม่ทำล่ะ?
เหตุใดฉันจึงควรใช้คำหลัก extends เสมอ ทำไมการใช้ super ถึงผิด?
แน่นอนว่าสิ่งเดียวกันนี้หมายถึงมุมมองของผู้บริโภค เหตุใดผู้บริโภคจึงควรเป็น super เสมอ


ปล.: หากต้องการให้เจาะจงยิ่งขึ้น คุณสามารถดูตัวอย่างข้างต้นได้ที่ส่วน "Item 28" ของหนังสือที่อ้างอิง


person LiTTle    schedule 05.06.2013    source แหล่งที่มา
comment
เมื่อคุณผลักดัน - คุณทำหน้าที่เป็น counsumer ไม่ใช่โปรดิวเซอร์ ดังนั้นคุณจึงจำเป็นต้องใช้คีย์เวิร์ด super   -  person Alexandr    schedule 05.06.2013
comment
ตามหนังสือฉันเป็นโปรดิวเซอร์ ประเภท Wildcard ของความคิดเห็นสำหรับพารามิเตอร์ที่ทำหน้าที่เป็นตัวสร้าง E นั้นเป็นของผู้เขียน ผู้เขียนบอกว่าฉันเป็นผู้บริโภคเมื่อฉันดึง Stack! สองตัวอย่างแรกมาจากหนังสือ (ก็อปปี้-วาง) ตัวอย่างที่สามคือของฉัน   -  person LiTTle    schedule 05.06.2013


คำตอบ (5)


เมื่อคุณประกาศ Stack<Foo> คุณหมายถึง Stack of Foos หรือคลาสย่อยของ Foo ตามตัวอย่าง คุณคาดหวังว่าจะสามารถใส่ String ใน Stack<Object> ได้ วิธีอื่นไม่เป็นความจริง คุณไม่ควรแทรก Object อื่นใน Stack<String> ได้

ในตัวอย่างของคุณ คุณประกาศ Stack<Integer> คุณควรใส่ Integers ลงในสแต็กนี้ได้ แต่ไม่ใช่ Numbers อื่นๆ (เช่น Double) ซึ่งคุณจะทำได้หากคุณประกาศพารามิเตอร์ <? super E> นั่นเป็นสาเหตุที่ put-method ควรมีพารามิเตอร์ประเภท <? extends E>

person Aleksander Blomskøld    schedule 05.06.2013

การพยายามจัดเก็บตัวเลขที่กำหนดเองในสแต็กไม่สามารถทำงานได้ เนื่องจากตัวเลขอาจเป็นอย่างอื่นที่ไม่ใช่จำนวนเต็ม ตัวอย่างของคุณจึงไม่สมเหตุสมผลนัก

คุณจะใช้ super เมื่ออ็อบเจ็กต์ asts ในฐานะผู้บริโภค เช่น เมื่ออินสแตนซ์ของอ็อบเจ็กต์ประเภททั่วไปถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังเมธอดของอ็อบเจ็กต์ ตัวอย่างเช่น:

 Collections.sort(List<T>, Comparator<? super T>)

ในตัวอย่างนี้ วิธีการเรียงลำดับจะนำอินสแตนซ์ T จากคอลเลกชัน และส่งผ่านเป็นอาร์กิวเมนต์ไปยัง compare(T o1, T o2) ของตัวเปรียบเทียบ

เปรียบเทียบสิ่งนี้กับตัวอย่างแรกของคุณ โดยที่ Iterable src เป็นผู้ผลิต เมธอด pushAll() เรียกเมธอดของ Iterable ซึ่งสร้าง (เช่น ส่งคืน) อินสแตนซ์ของ T ในกรณีนี้ iterable คือตัวสร้าง ดังนั้นการใช้ ? extends T

person JB Nizet    schedule 05.06.2013

ในเมธอด pushAll คุณไม่ได้ส่งประเภท E แต่เป็นประเภทใดๆ ที่ขยาย E ดังนั้น แทนที่จะส่ง Iterable จาก Numbers คุณสามารถส่งผ่าน Iterable ใดๆ ของประเภทที่ขยาย Number ได้

ตัวอย่างเดิมใช้ประเภท Number เนื่องจากคุณสามารถส่งประเภทใดก็ได้ที่เป็นคลาสย่อยของ Number เช่น Integer, BigDecimal และอื่นๆ

ในตัวอย่างของคุณ คุณกำลังทำอย่างอื่น คุณกำลังใช้ Integer เพื่อประกาศ Stack ของคุณ ดังนั้น pushAll จะสามารถยอมรับได้เฉพาะคลาสที่ขยายด้วย Integer เท่านั้น คุณจะไม่สามารถใช้ Numbers ได้ (หรือคลาสอื่นใด เนื่องจาก Integer เป็นคลาสสุดท้าย)

person ipavlic    schedule 05.06.2013

สิ่งแรกที่ต้องสังเกตก็คือ Integer จะขยาย Number ดังนั้นคุณไม่ควรผลักอ็อบเจ็กต์ Number ลงใน Stack of Integers อย่างไรก็ตาม ตัวอย่างแรกจะใช้ได้กับ Integers, Floats, BigDecimal และคลาสย่อย Number อื่นๆ ทั้งหมด

person Martin    schedule 05.06.2013

ตัวอย่างของคุณไม่สมเหตุสมผลมากนัก โครงสร้างเช่น <? extends Number> หมายความว่าอนุญาตให้ใช้ Number และทุกประเภท ซึ่งสืบทอดมาจาก Number ดังนั้น คุณจึงกำหนดขอบเขตบนและล่าง ตั้งแต่ประเภท Number ลงไปจนถึงขอบเขตเฉพาะที่สุด ในทางกลับกัน <? super Number> หมายความว่าอนุญาตให้ใช้ Number และ supertyes ใดๆ ได้ เนื่องจาก Number ขยาย Object และใช้ Serializable จึงอนุญาตให้ใช้สามประเภทต่อไปนี้:

  1. java.lang.Number
  2. java.lang.Object
  3. java.io.ซีเรียลไลซ์ได้

ในตัวอย่างของคุณ คุณประกาศประเภททั่วไป Stack<Integer> ลองพิจารณาสิ่งต่อไปนี้

  1. Stack ของคุณไม่สามารถเก็บไอเทม ประเภทซุปเปอร์ ที่เป็นจำนวนเต็มได้
  2. Stack ของคุณไม่สามารถเก็บรายการของ ประเภทย่อย ใดๆ ของ Integer ได้ เนื่องจากคลาส Integer ถือเป็นคลาสสุดท้าย จึงไม่สามารถจัดคลาสย่อยได้

ดังนั้น หากคุณต้องการประกาศประเภททั่วไป Stack<Integer> การวนซ้ำของคุณจะเป็นประเภท Iterable<Integer> ดังนั้น Stack ของคุณจะสามารถเท่านั้นเก็บรายการประเภท Integer คุณพูดถูกกับ PECS ที่ช่วยจำ แต่จะได้ผลก็ต่อเมื่อคุณเลือกประเภทที่เป็นรูปธรรมซึ่งมีประเภทซุปเปอร์อย่างน้อยหนึ่งประเภทและประเภทย่อยอย่างน้อยหนึ่งประเภท

person My-Name-Is    schedule 05.06.2013