ทำไม javac จึงอนุญาตให้ใช้งานได้


52

ถ้าฉันพยายามที่จะส่ง a Stringถึงjava.util.Date, คอมไพเลอร์ Java จับข้อผิดพลาด ดังนั้นทำไมคอมไพเลอร์ไม่ตั้งค่าสถานะต่อไปนี้เป็นข้อผิดพลาด?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

แน่นอน JVM โยนClassCastExceptionat runtime แต่คอมไพเลอร์ไม่ได้ตั้งค่าสถานะ

พฤติกรรมนี้เหมือนกันกับ javac 1.8.0_212 และ 11.0.2


2
ไม่มีอะไรพิเศษเกี่ยวกับListที่นี่ Date d = (Date) new Object();
Elliott Frisch

1
ฉันเล่นอาร์ดิโนเมื่อเร็ว ๆ นี้ ฉันชอบคอมไพเลอร์ที่ไม่ยอมรับนักแสดงอย่างมีความสุขและจากนั้นก็แสดงผลลัพธ์ที่ไม่สามารถคาดเดาได้ทั้งหมด สตริงเป็นจำนวนเต็ม? แน่นอน! สองเท่าถึงจำนวนเต็ม? ครับท่าน! สตริงที่บูลีน? อย่างน้อยที่สุดก็กลายเป็นเท็จ ...
Stian Yttervik

@ElliottFrisch: มีความสัมพันธ์การสืบทอดที่ชัดเจนระหว่างวันที่และวัตถุ แต่ไม่มีความสัมพันธ์ระหว่างวันที่และรายการ ดังนั้นฉันจึงคาดหวังให้คอมไพเลอร์ตั้งค่าสถานะนักแสดงนี้เช่นเดียวกับที่จะตั้งค่าสถานะนักแสดงจาก String to Date แต่ดังที่ Zabuza อธิบายด้วยคำตอบที่ยอดเยี่ยม List คืออินเทอร์เฟซดังนั้นนักแสดงจะถูกกฎหมายหากstrListเป็นตัวอย่างของคลาสที่ใช้ List
Mike Woinoski

นี่เป็นคำถามที่เกิดขึ้นบ่อยครั้งและฉันแน่ใจว่าฉันเห็นรายการซ้ำหลายรายการ โดยพื้นฐานแล้วมันเป็นเวอร์ชั่นย้อนกลับของความเกี่ยวข้องอย่างยิ่ง: stackoverflow.com/questions/21812289/ …
Hulk

1
@StianYttervik -fpermissive คือสิ่งที่ทำ เปิดคำเตือนของคอมไพเลอร์
bobsburner

คำตอบ:


86

เทคนิคการโยนสามารถทำได้ ไม่สามารถพิสูจน์ได้อย่างง่ายดายโดย javac ว่าไม่ใช่ในกรณีของคุณและ JLS กำหนดว่านี่เป็นโปรแกรม Java ที่ถูกต้องดังนั้นการตั้งค่าสถานะข้อผิดพลาดจะไม่ถูกต้อง

นี่เป็นเพราะListเป็นอินเทอร์เฟซ ดังนั้นคุณอาจมีคลาสย่อยของDateที่ใช้จริงListปลอมตัวเป็นListที่นี่ - แล้วหล่อมันDateจะตกลงอย่างสมบูรณ์ ตัวอย่างเช่น:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

แล้ว:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

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

โปรดทราบว่า JLS ต้องการให้รหัสของคุณเป็นโปรแกรม Java ที่ถูกต้อง ใน5.1.6.1 การแปลงอ้างอิงที่อนุญาตให้แคบลงนั้นระบุว่า:

การแปลงอ้างอิงกวดขันอยู่จากชนิดอ้างอิงSกับประเภทอ้างอิงTถ้าทั้งหมดต่อไปนี้เป็นจริง :

  • [ ... ]
  • กรณีใดกรณีหนึ่งต่อไปนี้ :
    • [ ... ]
    • Sเป็นประเภทอินเตอร์เฟสTเป็นประเภทคลาสและTไม่ตั้งชื่อfinalคลาส

ดังนั้นแม้ว่าคอมไพเลอร์สามารถเข้าใจว่ากรณีของคุณเป็นไปไม่ได้จริง ๆ แล้วมันไม่ได้รับอนุญาตให้ตั้งค่าสถานะข้อผิดพลาดเพราะ JLS กำหนดว่าเป็นโปรแกรม Java ที่ถูกต้อง

มันจะได้รับอนุญาตเท่านั้นที่จะแสดงคำเตือน


16
และน่าสังเกตว่าเหตุผลที่มันจับคดีกับ String นั้นคือ String นั้นสุดท้ายดังนั้นคอมไพเลอร์จึงรู้ว่าไม่มีคลาสใดสามารถขยายได้
MTilsted

5
ที่จริงแล้วฉันไม่คิดว่ามันเป็น "ขั้นสุดท้าย" ของ String ที่ทำให้myDate = (Date) myStringล้มเหลว การใช้คำศัพท์ JLS คำสั่งจะพยายามแปลงจากS( String) เป็นT( Date) นี่Sไม่ใช่ประเภทอินเตอร์เฟสดังนั้นเงื่อนไข JLS ที่ยกมาข้างต้นจึงไม่สามารถใช้งานได้ ยกตัวอย่างเช่นลองหล่อปฏิทินเป็นวันที่แล้วคุณจะได้รับข้อผิดพลาดในการคอมไพเลอร์
Mike Woinoski

1
ฉันไม่รู้ว่าผิดหวังหรือไม่ที่คอมไพเลอร์ไม่สามารถทำการวิเคราะห์แบบสแตติกได้เพียงพอเพื่อพิสูจน์ว่า strList สามารถเป็นประเภท ArrayList ได้หรือไม่
Joshua

3
คอมไพเลอร์ไม่ได้รับอนุญาตจากการตรวจสอบ แต่ห้ามมิให้เรียกมันว่ามีข้อผิดพลาด นั่นจะทำให้คอมไพเลอร์ไม่เข้ากัน (ดูคำตอบของฉัน ... )
Stephen C

3
ในการเพิ่มศัพท์แสงเล็ก ๆ น้อย ๆ คอมไพเลอร์จะต้องพิสูจน์ว่าประเภทDate & Listนั้นไม่สามารถใช้งานได้มันไม่เพียงพอที่จะพิสูจน์ได้ว่ามันไม่ได้อาศัยอยู่ในปัจจุบัน (อาจเป็นในอนาคต)
Polygnome

15

ขอให้เราพิจารณาตัวอย่างของคุณโดยทั่วไป:

List<String> strList = someMethod();       
Date d = (Date) strList;

นี่คือสาเหตุหลักที่ทำให้Date d = (Date) strList;ไม่มีข้อผิดพลาดในการรวบรวม

  • เหตุผลที่ใช้งานง่ายก็คือว่าคอมไพเลอร์ไม่ได้ (ทั่วไป) ทราบชนิดที่แม่นยำของวัตถุที่ส่งกลับโดยเรียกวิธีการที่ เป็นไปได้ว่านอกจากจะเป็นระดับที่ดำเนินการListมันเป็นยัง subclass Dateของ

  • เหตุผลทางเทคนิคคือ Java Language ข้อกำหนด "ช่วยให้" การแปลงอ้างอิงตีบที่สอดคล้องกับประเภทหล่อนี้ ตามJLS 5.1.6.1 :

    "การแปลงการอ้างอิงที่ จำกัด นั้นมีอยู่จากประเภทSการอ้างอิงเป็นประเภทการอ้างอิงTหากทั้งหมดต่อไปนี้เป็นจริง:"

    ...

    5) " Sเป็นประเภทอินเตอร์เฟสTเป็นประเภทคลาสและTไม่ตั้งชื่อfinalคลาส"

    ...

    ในสถานที่อื่น JLS ยังบอกด้วยว่าอาจมีข้อยกเว้นเกิดขึ้นขณะรันไทม์ ...

    โปรดทราบว่าการกำหนด JLS 5.1.6.1 จะขึ้นอยู่แต่เพียงผู้เดียวในประเภทประกาศของตัวแปรที่เกี่ยวข้องมากกว่าประเภทรันไทม์ที่เกิดขึ้นจริง ในกรณีทั่วไปคอมไพเลอร์ไม่ทราบชนิดรันไทม์จริง


ดังนั้นทำไมคอมไพเลอร์ Java ไม่สามารถทำงานได้ว่านักแสดงจะไม่ทำงาน?

  • ในตัวอย่างของฉันการsomeMethodโทรสามารถส่งคืนวัตถุที่มีหลายประเภท แม้ว่าคอมไพเลอร์สามารถวิเคราะห์เนื้อความของเมธอดและตรวจสอบชุดของประเภทที่สามารถส่งคืนได้อย่างแม่นยำ แต่ก็ไม่มีอะไรที่จะหยุดใครบางคนที่เปลี่ยนมันให้กลับมาเป็นประเภทต่าง ๆ ... หลังจากรวบรวมรหัสที่เรียกมัน นี่คือเหตุผลพื้นฐานว่าทำไม JLS 5.1.6.1 บอกว่ามันพูดอะไร

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

แล้วทำไมคอมไพเลอร์อัจฉริยะไม่อนุญาตให้บอกว่านี่เป็นข้อผิดพลาดล่ะ?

  • เพราะ JLS บอกว่านี่เป็นโปรแกรมที่ถูกต้อง ระยะเวลา คอมไพเลอร์ใด ๆ ที่เรียกสิ่งนี้ว่าข้อผิดพลาดจะไม่สอดคล้องกับ Java

  • นอกจากนี้คอมไพเลอร์ใด ๆ ที่ปฏิเสธโปรแกรม Java ที่ JLS และคอมไพเลอร์อื่น ๆบอกว่าถูกต้องเป็นอุปสรรคต่อการพกพาของซอร์สโค้ด Java


4
การลงทะเบียนสำหรับความจริงที่ว่าหลังจากรวบรวมคลาสที่เรียกใช้งานฟังก์ชั่นที่เรียกว่าอาจมีการเปลี่ยนแปลงดังนั้นแม้ว่ามันจะพิสูจน์ได้ในเวลารวบรวมกับการดำเนินการปัจจุบันของ callee ว่าโยนเป็นไปไม่ได้นี้อาจไม่เป็นเช่นนั้นในภายหลังเมื่อผู้ถูกเปลี่ยนหรือถูกแทนที่
Peter - Reinstate Monica

2
โหวตขึ้นสำหรับการเน้นปัญหาการพกพาที่จะนำมาใช้ถ้าคอมไพเลอร์พยายามฉลาดเกินไป
Mike Woinoski

2

5.5.1 อ้างอิงประเภทหล่อ:

กำหนดประเภทการอ้างอิงเวลาคอมไพล์S(แหล่งที่มา) และประเภทการอ้างอิงเวลาคอมไพล์T(เป้าหมาย) การแปลงแคสต์นั้นมีอยู่ตั้งแต่Sถึง Tหากไม่มีข้อผิดพลาดเวลาคอมไพล์เกิดขึ้นเนื่องจากกฎต่อไปนี้

[ ... ]

หากSเป็นประเภทอินเตอร์เฟส:

  • [ ... ]

  • ถ้าTเป็นประเภทคลาสหรือส่วนต่อประสานที่ไม่ใช่ครั้งสุดท้ายถ้ามีประเภทXของsupertype Tและ supertype Yของ Sเช่นนั้นทั้งคู่XและYเป็นประเภทพารามิเตอร์แปรปรวนที่แตกต่างกันและการลบXและYเหมือนกันข้อผิดพลาดในการรวบรวมเวลา เกิดขึ้น

    มิฉะนั้นนักแสดงจะถูกกฎหมายเสมอ ณ เวลารวบรวม (เพราะแม้ว่าTจะไม่ได้นำไปใช้Sคลาสย่อยของTอาจ)

List<String>เป็นSและDateเป็นTในกรณีของคุณ

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