Downcasting อัตโนมัติโดยการอนุมานประเภท


11

ในจาวาคุณต้องส่งอย่างชัดเจนเพื่อลดตัวแปร

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

มีเหตุผลสำหรับข้อกำหนดนี้นอกเหนือจากข้อเท็จจริงที่ว่า Java ไม่ได้ทำการอนุมานประเภทใด?

มี "gotchas" ในการติดตั้ง downcasting อัตโนมัติในภาษาใหม่หรือไม่?

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

Apple child = parent; // no cast required 

คุณกำลังบอกว่าถ้าคอมไพเลอร์สามารถอนุมานได้ว่าวัตถุที่ถูกดาวน์สตรีมนั้นเป็นประเภทที่ถูกต้องเสมอคุณควรจะไม่สามารถแสดงดาวน์ไลท์ได้อย่างชัดเจน? แต่จากนั้นขึ้นอยู่กับเวอร์ชั่นของคอมไพเลอร์และการอนุมานประเภทว่ามันสามารถทำบางโปรแกรมได้อาจไม่ถูกต้อง ... ข้อบกพร่องในตัวพิมพ์เล็ก ๆ สามารถป้องกันโปรแกรมที่ถูกต้องจากการเขียน ฯลฯ ... มันไม่สมเหตุสมผลที่จะแนะนำพิเศษบางอย่าง กรณีที่ขึ้นอยู่กับหลายปัจจัยผู้ใช้จะไม่สามารถหยั่งรู้ได้หากพวกเขาต้องการเพิ่มหรือลบการร่ายโดยไม่ต้องมีเหตุผลทุกครั้งที่ปล่อย
Bakuriu

คำตอบ:


24

อัพแคสต์ประสบความสำเร็จเสมอ

Downcasts อาจส่งผลให้เกิดข้อผิดพลาดรันไทม์เมื่อชนิดรันไทม์ของวัตถุไม่ได้เป็นประเภทย่อยของประเภทที่ใช้ในการโยน

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

เมื่อระบบประเภทมีความกังวล upcasts ใส่ภาระของการพิสูจน์ในคอมไพเลอร์ (ซึ่งจะต้องตรวจสอบแบบคงที่) downcasts ใส่ภาระของการพิสูจน์ในโปรแกรมเมอร์ (ซึ่งต้องคิดอย่างหนักเกี่ยวกับเรื่องนี้)

หนึ่งได้ยืนยันว่าการเขียนโปรแกรมภาษาออกแบบอย่างถูกต้องจะห้าม downcasts Option<T>สมบูรณ์หรือให้บรรยากาศทางเลือกที่ปลอดภัยเช่นกลับชนิดไม่จำเป็น อย่างไรก็ตามภาษาที่แพร่หลายหลายภาษาเลือกวิธีที่ง่ายกว่าและใช้งานได้ง่ายกว่าในการส่งคืนTและทำให้เกิดข้อผิดพลาดอย่างอื่น

ในตัวอย่างเฉพาะของคุณคอมไพเลอร์อาจได้รับการออกแบบมาเพื่ออนุมานว่าparentจริงๆแล้วเป็นการAppleวิเคราะห์แบบสแตติกอย่างง่ายและอนุญาตให้นักแสดงโดยปริยาย อย่างไรก็ตามโดยทั่วไปปัญหาจะไม่สามารถตัดสินใจได้ดังนั้นเราจึงไม่สามารถคาดหวังให้คอมไพเลอร์แสดงเวทย์มนตร์มากเกินไป


1
เพียงสำหรับการอ้างอิงตัวอย่างของภาษานั้น downcasts เพื่อ optionals เป็นสนิม แต่นั่นเป็นเพราะมันไม่ได้มีมรดกที่แท้จริงเพียง"ชนิดใด ๆ"
Kroltan

3

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

ในสถานการณ์เช่นตัวอย่างของวัตถุที่ถูกสร้างขึ้นในฐานะที่เป็นAppleแล้วว่ามีความรู้ที่ถูกโยนออกไปโดยการจัดเก็บ refernce Fruitในตัวแปรของชนิด จากนั้นคุณต้องการใช้การอ้างอิงแบบเดียวกันAppleอีกครั้ง

เนื่องจากข้อมูลที่ถูกโยนออกไปเท่านั้น "ในประเทศ" แน่ใจว่าคอมไพเลอร์จะสามารถรักษาความรู้ที่parentเป็นจริงแม้ว่าประเภทประกาศของมันคือAppleFruit

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

เมื่อคุณมีFruitและต้องการที่จะใช้เป็นAppleก็มักจะหมายความว่าคุณได้รับFruitผ่านบางหมายความว่าโดยทั่วไปจะกลับชนิดของใด ๆแต่ในกรณีนี้คุณจะรู้ว่ามันเป็นFruit Appleเกือบทุกครั้งที่คุณไม่ได้สร้างมันขึ้นมาคุณก็ผ่านมันไปได้ด้วยรหัสอื่น

ตัวอย่างที่ชัดเจนคือถ้าฉันมีparseFruitฟังก์ชั่นที่สามารถเปลี่ยนสตริงเช่น "แอปเปิ้ล", "ส้ม", "มะนาว" ฯลฯ ลงในคลาสย่อยที่เหมาะสม; โดยทั่วไปสิ่งที่เรา (และคอมไพเลอร์) สามารถรู้เกี่ยวกับฟังก์ชั่นนี้ก็คือมันจะส่งกลับบางชนิดFruitแต่ถ้าฉันโทรparseFruit("apple")แล้วฉันรู้ว่ามันจะเรียกAppleและอาจต้องการที่จะใช้Appleวิธีการดังนั้นฉันสามารถ downcast

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

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


3

มันเป็นเรื่องของการที่คุณต้องการวาดเส้น คุณสามารถออกแบบภาษาที่จะตรวจสอบความถูกต้องของ downcast โดยนัย:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

ตอนนี้ให้แยกวิธี:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

เรายังดีอยู่ การวิเคราะห์แบบคงที่นั้นยากกว่า แต่ก็ยังทำได้

แต่ปัญหาปรากฏขึ้นบางคนที่สองเพิ่ม:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

ตอนนี้คุณต้องการเพิ่มข้อผิดพลาดที่ไหน สิ่งนี้สร้างภาวะที่กลืนไม่เข้าคายไม่ออกเราสามารถพูดได้ว่าปัญหานั้นเกิดขึ้นApple child = parent;แต่สิ่งนี้สามารถโต้แย้งได้โดย "แต่มันใช้ได้ผลมาก่อน" จากการเพิ่มการเพิ่ม eat(trouble);ทำให้เกิดปัญหา "แต่จุดรวมของความหลากหลายคือการอนุญาตให้ตรง

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

BTW ใน Java downcast ที่คุณอธิบายไม่ใช่การ downcast นี่คือการโยนทั่วไปซึ่งสามารถส่งแอปเปิ้ลไปยังส้มเช่นกัน ดังนั้นความคิดทางเทคนิคในการพูด @ chi ของที่นี่มาแล้วไม่มี downcasts ใน Java เพียง "ทุก ๆ คาสต์" มันจะสมเหตุสมผลในการออกแบบตัวดำเนินการ downcast เฉพาะซึ่งจะโยนข้อผิดพลาดการคอมไพล์เมื่อเป็นประเภทผลลัพธ์ไม่พบ downstream จากประเภทอาร์กิวเมนต์ มันเป็นความคิดที่ดีที่จะทำให้ "ทุกคาสต์" ยากต่อการใช้งานมากขึ้นเพื่อกีดกันโปรแกรมเมอร์จากการใช้งานโดยไม่มีเหตุผลที่ดีมาก XXXXX_cast<type>(argument)ไวยากรณ์ของ C ++ เป็นที่จดจำ

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