เมธอดมีการลบแบบเดียวกับเมธอดชนิดอื่น


381

ทำไมมันไม่ถูกกฎหมายที่จะมีสองวิธีต่อไปนี้ในชั้นเรียนเดียวกัน

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

ฉันได้รับ compilation error

วิธีการเพิ่ม (ชุด) มีการเพิ่มการลบเช่นเดียวกับชุดวิธีการอื่นในการทดสอบประเภท

ในขณะที่ฉันสามารถหลีกเลี่ยงได้ฉันก็สงสัยว่าทำไม javac ไม่ชอบสิ่งนี้

ฉันเห็นได้ว่าในหลาย ๆ กรณีตรรกะของทั้งสองวิธีนั้นจะคล้ายกันมากและอาจถูกแทนที่ด้วยวิธีเดียว

public void add(Set<?> set){}

วิธีการ แต่นี่ไม่ใช่กรณี

นี่เป็นเรื่องที่น่ารำคาญเป็นพิเศษหากคุณต้องการมีสองอันconstructorsที่ขัดแย้งกันเพราะคุณไม่สามารถเปลี่ยนชื่อของหนึ่งในconstructorsนั้นได้


1
ถ้าคุณไม่มีโครงสร้างข้อมูลและคุณยังต้องการเวอร์ชันมากกว่านี้
Omry Yadan

1
คุณสามารถสร้างคลาสแบบกำหนดเองที่สืบทอดจากเวอร์ชันพื้นฐาน
willlma

1
OP คุณคิดวิธีแก้ปัญหาคอนสตรัคเตอร์บ้างไหม? ฉันต้องยอมรับสองแบบListและฉันไม่รู้วิธีจัดการมัน
Tomáš Zato - Reinstate Monica

9
เมื่อทำงานกับ Java ฉันคิดถึง C # ...
Svish

1
@ TomášZato, ฉันแก้ไขได้โดยการเพิ่ม paramour dummy ใน Constructor: Boolean noopSignatureOverload
Nthalk

คำตอบ:


357

กฎนี้มีวัตถุประสงค์เพื่อหลีกเลี่ยงความขัดแย้งในรหัสดั้งเดิมที่ยังคงใช้ประเภทดิบ

นี่คือภาพประกอบว่าทำไมสิ่งนี้จึงไม่ได้รับอนุญาตดึงมาจาก JLS สมมติว่าก่อนที่ generics จะแนะนำให้รู้จักกับ Java ฉันเขียนโค้ดบางอย่างเช่นนี้:

class CollectionConverter {
  List toList(Collection c) {...}
}

คุณขยายชั้นเรียนของฉันเช่นนี้

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

หลังจากการแนะนำ generics ฉันตัดสินใจที่จะปรับปรุงห้องสมุดของฉัน

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

คุณยังไม่พร้อมที่จะทำการอัพเดทใด ๆ ดังนั้นคุณจึงออกจากOverriderชั้นเรียนเพียงลำพัง เพื่อที่จะลบล้างtoList()วิธีการได้อย่างถูกต้องนักออกแบบภาษาตัดสินใจว่าประเภท raw นั้น "override-สมมูล" กับชนิดที่สร้างขึ้นใด ๆ ซึ่งหมายความว่าแม้ว่าลายเซ็นวิธีการของคุณจะไม่เป็นทางการเท่ากับลายเซ็น superclass ของฉันอีกต่อไปวิธีการของคุณยังคงแทนที่

เวลาผ่านไปและคุณตัดสินใจว่าคุณพร้อมที่จะอัปเดตชั้นเรียนของคุณ แต่คุณทำผิดพลาดเล็กน้อยและแทนที่จะแก้ไขtoList()วิธีraw ที่มีอยู่เดิมคุณเพิ่มวิธีการใหม่ดังนี้:

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

เนื่องจากความเท่าเทียมกันของการแทนที่ประเภทดิบทั้งสองวิธีอยู่ในรูปแบบที่ถูกต้องเพื่อแทนที่toList(Collection<T>)วิธีการ แต่แน่นอนว่าคอมไพเลอร์จำเป็นต้องแก้ไขวิธีการเดียว เพื่อกำจัดความกำกวมนี้คลาสไม่ได้รับอนุญาตให้มีหลายวิธีที่เทียบเท่ากับการแทนที่นั่นคือหลายวิธีที่มีพารามิเตอร์ชนิดเดียวกันหลังจากลบออก

กุญแจสำคัญคือนี่เป็นกฎภาษาที่ออกแบบมาเพื่อรักษาความเข้ากันได้กับรหัสเดิมโดยใช้ชนิดข้อมูลดิบ ไม่ใช่ข้อ จำกัด ที่ต้องการโดยการลบพารามิเตอร์ชนิด เนื่องจากวิธีแก้ไขปัญหาเกิดขึ้นในเวลาคอมไพล์การเพิ่มประเภททั่วไปลงในตัวระบุวิธีการจะเพียงพอ


3
คำตอบและตัวอย่างที่ยอดเยี่ยม! ฉันไม่แน่ใจอย่างไรก็ตามถ้าฉันเข้าใจประโยคสุดท้ายของคุณอย่างสมบูรณ์ ("เนื่องจากวิธีการแก้ปัญหาเกิดขึ้นในเวลาคอมไพล์ก่อนที่จะลบการแก้ไขประเภทจึงไม่จำเป็นต้องใช้เพื่อให้สามารถใช้งานได้") คุณอธิบายรายละเอียดเล็กน้อยได้ไหม?
Jonas Eicher

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

1
@daveloyall javacไม่ฉันไม่ได้ตระหนักถึงตัวเลือกดังกล่าว
erickson

13
ไม่ใช่ครั้งแรกที่ฉันพบข้อผิดพลาด Java ซึ่งไม่มีข้อผิดพลาดเลยและสามารถรวบรวมได้หากมีเพียงผู้เขียน Java เท่านั้นที่ใช้คำเตือนเหมือนคนอื่น ๆ พวกเขาเท่านั้นที่คิดว่าพวกเขารู้ทุกอย่างดีกว่า
Tomáš Zato - Reinstate Monica

1
@ TomášZatoไม่ฉันไม่รู้วิธีการทำความสะอาดใด ๆ หากคุณต้องการสิ่งที่สกปรกคุณสามารถผ่านList<?>และenumสิ่งที่คุณกำหนดเพื่อระบุประเภท ตรรกะชนิดเฉพาะอาจเป็นวิธีการใน enum หรือคุณอาจต้องการสร้างคลาสที่แตกต่างกันสองคลาสแทนที่จะเป็นคลาสเดียวที่มีคอนสตรัคเตอร์ต่างกันสองตัว ตรรกะทั่วไปจะอยู่ใน superclass หรือวัตถุตัวช่วยซึ่งทั้งสองประเภทมอบหมาย
erickson

117

Java generics ใช้การลบประเภท บิตในวงเล็บเหลี่ยม ( <Integer>และ<String>) ถูกลบออกดังนั้นคุณจะได้สองวิธีที่มีลายเซ็นเหมือนกัน (ที่add(Set)คุณเห็นในข้อผิดพลาด) ไม่ได้รับอนุญาตเพราะรันไทม์จะไม่ทราบว่าจะใช้สำหรับแต่ละกรณี

หาก Java เคยได้รับการแปลใหม่ทั่วไปคุณก็สามารถทำได้ แต่ตอนนี้อาจไม่น่าเป็นไปได้


24
ฉันขอโทษ แต่คำตอบของคุณ (และคำตอบอื่น ๆ ) ไม่ได้อธิบายว่าทำไมมีข้อผิดพลาดที่นี่ การคอมไพล์เกินพิกัดเสร็จสิ้นในเวลาคอมไพเลอร์และคอมไพเลอร์มีข้อมูลประเภทที่จำเป็นในการตัดสินใจว่าวิธีใดที่จะเชื่อมโยงตามที่อยู่หรือโดยวิธีใดก็ตามที่มีการอ้างอิงในไบต์ซึ่งฉันเชื่อว่าไม่ใช่ลายเซ็น ฉันยังคิดว่าคอมไพเลอร์บางคนจะอนุญาตให้รวบรวม
Stilgar

5
@Stilgar มีวิธีการหยุดวิธีการที่เรียกหรือตรวจสอบผ่านการสะท้อน? รายการเมธอดที่ส่งคืนโดย Class.getMethods () จะมีสองวิธีที่เหมือนกันซึ่งไม่สมเหตุสมผล
Adrian Mouat

5
ข้อมูลการสะท้อนสามารถ / ควรมีข้อมูลเมตาที่จำเป็นในการทำงานกับข้อมูลทั่วไป ถ้าไม่ใช่คอมไพเลอร์ Java จะรู้เกี่ยวกับวิธีการทั่วไปอย่างไรเมื่อคุณอิมพอร์ตไลบรารีที่คอมไพล์แล้ว?
Stilgar

4
จากนั้นเมธอด getMethod ต้องการแก้ไข ตัวอย่างเช่นแนะนำการโอเวอร์โหลดซึ่งระบุการโอเวอร์โหลดทั่วไปและทำให้เมธอดดั้งเดิมส่งคืนเวอร์ชันที่ไม่ใช่ทั่วไปเท่านั้นไม่ส่งคืนเมธอดใด ๆ ที่บันทึกเป็นแบบทั่วไป แน่นอนว่าควรทำในรุ่น 1.5 หากพวกเขาทำตอนนี้พวกเขาจะทำลายความเข้ากันได้ย้อนหลังของวิธีการ ฉันยืนตามแถลงการณ์ของฉันว่าการลบประเภทไม่ได้กำหนดพฤติกรรมนี้ มันเป็นการใช้งานที่ไม่ได้รับงานเพียงพออาจเกิดจากทรัพยากรที่ จำกัด
Stilgar

1
นี่ไม่ใช่คำตอบที่แม่นยำ แต่จะสรุปปัญหาในนิยายที่มีประโยชน์อย่างรวดเร็ว: ลายเซ็นของวิธีการนั้นคล้ายคลึงกันมากเกินไปคอมไพเลอร์อาจไม่บอกความแตกต่างจากนั้นคุณจะได้รับ "ปัญหาการรวบรวมที่ไม่ได้แก้ไข"
พุธที่

46

เพราะนี่คือ Java Generics จะดำเนินการกับประเภทลบ

วิธีการของคุณจะได้รับการแปลในบางครั้งเช่น:

วิธีการแก้ปัญหาเกิดขึ้นในเวลารวบรวมและไม่ได้พิจารณาพารามิเตอร์ประเภท ( ดูคำตอบของ erickson )

void add(Set ii);
void add(Set ss);

ทั้งสองวิธีมีลายเซ็นเดียวกันโดยไม่มีพารามิเตอร์ประเภทดังนั้นข้อผิดพลาด


21

ปัญหาคือว่าSet<Integer>และSet<String>ได้รับการปฏิบัติจริงSetจาก JVM การเลือกประเภทสำหรับชุด (สตริงหรือจำนวนเต็มในกรณีของคุณ) เป็นเพียงน้ำตาลที่ใช้โดยคอมไพเลอร์ JVM ที่ไม่สามารถแยกแยะความแตกต่างระหว่างและSet<String>Set<Integer>


7
เป็นความจริงที่ JVM รันไทม์ไม่มีข้อมูลที่จะแยกแยะความแตกต่างSetแต่เนื่องจากวิธีการแก้ปัญหาที่เกิดขึ้นในเวลารวบรวมเมื่อข้อมูลที่จำเป็นมีอยู่สิ่งนี้ไม่เกี่ยวข้อง ปัญหาคือการยอมให้โอเวอร์โหลดเหล่านี้ขัดแย้งกับค่าเผื่อสำหรับประเภทดิบดังนั้นจึงถูกทำให้ผิดกฎหมายในไวยากรณ์ Java
erickson

@erickson ถึงแม้คอมไพเลอร์จะรู้ว่าวิธีการใดในการโทรแต่ทว่ามันก็ไม่สามารถทำได้เหมือนใน bytecode แต่ทั้งคู่ก็ดูเหมือนกันหมด คุณต้องเปลี่ยนวิธีการเรียกวิธีการที่ระบุเนื่องจาก(Ljava/util/Collection;)Ljava/util/List;ไม่ทำงาน คุณสามารถใช้ (Ljava/util/Collection<String>;)Ljava/util/List<String>;แต่นั่นเป็นการเปลี่ยนแปลงที่เข้ากันไม่ได้และคุณจะพบกับปัญหาที่ไม่สามารถแก้ไขได้ในสถานที่ที่คุณมีประเภทที่ถูกลบ คุณอาจต้องลบการลบทิ้งทั้งหมด แต่มันค่อนข้างซับซ้อน
maaartinus

@maaartinus ใช่ฉันยอมรับว่าคุณต้องเปลี่ยนวิธีการระบุ ฉันพยายามหาปัญหาที่แก้ไม่ได้บางอย่างที่ทำให้พวกเขายอมแพ้ต่อความพยายามนั้น
erickson

7

กำหนดวิธีการเดียวโดยไม่ต้องพิมพ์เช่น void add(Set ii){}

คุณสามารถพูดถึงประเภทในขณะที่เรียกวิธีการตามที่คุณเลือก มันจะใช้ได้กับชุดทุกประเภท


3

อาจเป็นไปได้ว่าคอมไพเลอร์แปล Set (Integer) เป็น Set (Object) ในโค้ดจาวาไบต์ หากเป็นกรณีนี้ Set (Integer) จะถูกใช้เฉพาะที่คอมไพล์เฟสสำหรับการตรวจสอบไวยากรณ์


5
Setมันเป็นเพียงแค่ทางเทคนิคชนิดดิบ ยาสามัญไม่มีในรหัสไบต์พวกเขากำลังน้ำตาลทรายสำหรับการหล่อและให้ความปลอดภัยชนิดเวลารวบรวม
Andrzej Doyle

1

ฉันชนนี่เมื่อพยายามเขียนสิ่งที่ชอบ: Continuable<T> callAsync(Callable<T> code) {....} และ Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} พวกเขากลายเป็นคอมไพเลอร์สำหรับคำจำกัดความ 2 ของ Continuable<> callAsync(Callable<> veryAsyncCode) {...}

การลบประเภทหมายถึงการลบข้อมูลอาร์กิวเมนต์ชนิดโดยทั่วไป นี่เป็นสิ่งที่น่ารำคาญมาก แต่นี่เป็นข้อ จำกัด ที่จะใช้กับ Java ในขณะนี้ สำหรับกรณีที่คอนสตรัคเตอร์ไม่สามารถทำได้มากคลาสย่อยใหม่ 2 อันที่มีพารามิเตอร์ต่างกันในคอนสตรัคเตอร์ หรือใช้วิธีการเริ่มต้นแทน ... (ตัวสร้างเสมือน?) ที่มีชื่อแตกต่างกัน ...

สำหรับวิธีการดำเนินการเปลี่ยนชื่อที่คล้ายกันจะช่วยเช่น

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

หรือด้วยชื่อที่เป็นคำอธิบายเพิ่มเติมการจัดทำเอกสารด้วยตนเองสำหรับกรณี oyu เช่นaddNamesและaddIndexesเช่นนั้น

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