ยกตัวอย่างฟังก์ชันที่แสดงให้เห็นถึงความแปรปรวนร่วมและความขัดแย้งในกรณีของการโอเวอร์โหลดและการแทนที่ใน Java? [ปิด]


105

โปรดแสดงตัวอย่างที่ดีสำหรับความแปรปรวนร่วมและความแตกต่างใน Java

คำตอบ:


155

ความแปรปรวนร่วม:

class Super {
  Object getSomething(){}
}
class Sub extends Super {
  String getSomething() {}
}

Sub # getSomething เป็น covariant เนื่องจากส่งคืนคลาสย่อยของประเภทผลตอบแทนของ Super # getSomething (แต่เติมเต็มสัญญาของ Super.getSomething ())

ความแตกต่าง

class Super{
  void doSomething(String parameter)
}
class Sub extends Super{
  void doSomething(Object parameter)
}

Sub # doSomething ไม่แปรผันเนื่องจากใช้พารามิเตอร์ของซูเปอร์คลาสของพารามิเตอร์ของ Super # doSomething (แต่อีกครั้งเติมเต็มสัญญาของ Super # doSomething)

หมายเหตุ: ตัวอย่างนี้ใช้ไม่ได้ใน Java คอมไพเลอร์ Java จะโอเวอร์โหลดและไม่แทนที่ doSomething () - Method ภาษาอื่น ๆ สนับสนุนรูปแบบความขัดแย้งนี้

Generics

สิ่งนี้เป็นไปได้สำหรับ Generics:

List<String> aList...
List<? extends Object> covariantList = aList;
List<? super String> contravariantList = aList;

ตอนนี้คุณสามารถเข้าถึงวิธีการทั้งหมดcovariantListที่ไม่ใช้พารามิเตอร์ทั่วไป (เนื่องจากต้องเป็น "ขยายวัตถุ") แต่ getters จะทำงานได้ดี (เนื่องจากวัตถุที่ส่งคืนจะเป็นประเภท "วัตถุ" เสมอ)

สิ่งที่ตรงกันข้ามเป็นจริงสำหรับcontravariantList: คุณสามารถเข้าถึงเมธอดทั้งหมดด้วยพารามิเตอร์ทั่วไป (คุณรู้ว่าต้องเป็นซูเปอร์คลาสของ "สตริง" ดังนั้นคุณจึงสามารถส่งผ่านได้เสมอ) แต่ไม่มีตัวรับ (ประเภทที่ส่งคืนอาจเป็นซุปเปอร์ไทป์อื่น ๆ ของสตริง )


79
ตัวอย่างแรกของความแตกต่างใช้ไม่ได้ใน Java doSomething () ในคลาสย่อยคือโอเวอร์โหลดไม่ใช่การแทนที่
Craig P.Motlin

15
แน่นอน. Java ไม่สนับสนุนข้อโต้แย้งที่ไม่ตรงกันในการพิมพ์ย่อย เฉพาะความแปรปรวนร่วมสำหรับประเภทการส่งคืนเมธอดที่เกี่ยวข้องเท่านั้น (เช่นในตัวอย่างแรก)
the_dark_destructor

คำตอบที่ดี ความแปรปรวนร่วมดูมีเหตุผลสำหรับฉัน แต่คุณช่วยชี้ย่อหน้าใน JLS ซึ่งอธิบายความขัดแย้งได้ไหม เหตุใดจึงเรียก Sub.doSomething
Mikhail

2
ดังที่ Craig ชี้ให้เห็นมันไม่ใช่ ฉันคิดว่านี่คือการปะทะกันระหว่างการเอาชนะและการโอเวอร์โหลดและ SUN ได้เลือก (เช่นเคย) ตัวเลือกที่เข้ากันได้แบบย้อนหลัง ดังนั้นใน Java คุณไม่สามารถใช้พารามิเตอร์ contravariant เมื่อแทนที่วิธีการ
ฮาร์ดโค้ด

1
คุณยินดีที่ได้ทราบว่าเหตุใดฉันจึงได้รับคะแนนโหวตสำหรับคำตอบของฉัน
ฮาร์ดโค้ด

48

ความแปรปรวนร่วม: Iterable และ Iterator การกำหนดตัวแปรร่วมIterableหรือIterator. Iterator<? extends T>สามารถนำมาใช้เช่นเดียวกับที่Iterator<T>- สถานที่เดียวที่พารามิเตอร์ชนิดปรากฏเป็นประเภทผลตอบแทนจากวิธีการเพื่อที่จะสามารถได้อย่างปลอดภัยขึ้นโยนnext Tแต่ถ้าคุณมีการSขยายTคุณสามารถกำหนดให้Iterator<S>กับตัวแปรประเภทIterator<? extends T>ได้ ตัวอย่างเช่นหากคุณกำลังกำหนดวิธีการค้นหา:

boolean find(Iterable<Object> where, Object what)

คุณจะไม่สามารถเรียกมันด้วยList<Integer>และ5ดังนั้นจึงนิยามได้ดีกว่าว่า

boolean find(Iterable<?> where, Object what)

Contra-variance: ตัวเปรียบเทียบ มันมักจะทำให้รู้สึกถึงการใช้งานเพราะมันสามารถนำมาใช้เช่นเดียวกับที่Comparator<? super T> Comparator<T>พารามิเตอร์ type ปรากฏเป็นcompareประเภทพารามิเตอร์ method เท่านั้นดังนั้นจึงTสามารถส่งผ่านไปได้อย่างปลอดภัย ตัวอย่างเช่นหากคุณมีDateComparator implements Comparator<java.util.Date> { ... }และต้องการจัดเรียงไฟล์List<java.sql.Date>ด้วยตัวเปรียบเทียบนั้น ( java.sql.Dateเป็นคลาสย่อยของjava.util.Date) คุณสามารถทำได้ด้วย:

<T> void sort(List<T> what, Comparator<? super T> how)

แต่ไม่ใช่ด้วย

<T> void sort(List<T> what, Comparator<T> how)

-4

ดูที่หลักการทดแทน Liskov มีผลถ้าคลาส B ขยายคลาส A คุณควรจะใช้ B ได้ทุกเมื่อที่ต้องการ A


6
นี่ไม่ได้ตอบคำถามและทำให้เข้าใจผิด เป็นไปได้ทั้งหมดที่จะออกแบบระบบตัวแปรที่ทำลายความถูกต้องทางความหมายดังนั้นจึงละเมิด LSP
Matt Whipple

นี่ไม่ใช่กรณีสำหรับการcontra variantพูด ไม่สามารถถูกแทนที่ด้วยsuper.doSomething("String") sub.doSomething(Object)
zinking

ไม่ใช่คำถาม
OlivierTerrien
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.