Java Casting แนะนำค่าใช้จ่ายหรือไม่? ทำไม?


105

มีค่าใช้จ่ายหรือไม่เมื่อเราโยนวัตถุประเภทหนึ่งไปยังอีกประเภทหนึ่ง? หรือคอมไพเลอร์เพียงแก้ไขทุกอย่างและไม่มีค่าใช้จ่ายในขณะทำงาน?

นี่เป็นเรื่องทั่วไปหรือมีหลายกรณี?

ตัวอย่างเช่นสมมติว่าเรามีอาร์เรย์ของ Object [] โดยที่แต่ละองค์ประกอบอาจมีประเภทที่แตกต่างกัน แต่เรารู้แน่นอนอยู่แล้วว่าองค์ประกอบ 0 เป็นสองเท่าองค์ประกอบ 1 คือสตริง (ฉันรู้ว่านี่เป็นการออกแบบที่ผิด แต่สมมติว่าฉันต้องทำสิ่งนี้)

ข้อมูลประเภทของ Java ยังคงถูกเก็บไว้ตลอดเวลาหรือไม่ หรือทุกอย่างถูกลืมไปหลังจากการคอมไพล์แล้วและถ้าเราทำ (Double) องค์ประกอบ [0] เราก็จะทำตามตัวชี้และแปลความหมายของ 8 ไบต์นั้นเป็นสองเท่าคืออะไร?

ฉันไม่ชัดเจนมากเกี่ยวกับวิธีการสร้างประเภทใน Java หากคุณมีคำแนะนำใหม่เกี่ยวกับหนังสือหรือบทความก็ขอบคุณเช่นกัน


ประสิทธิภาพของอินสแตนซ์และแคสติ้งค่อนข้างดี ฉันโพสต์เวลาใน Java7 เกี่ยวกับวิธีการต่างๆในการแก้ปัญหาที่นี่: stackoverflow.com/questions/16320014/…
Wheezil

คำถามอื่น ๆ นี้มีคำตอบที่ดีมากstackoverflow.com/questions/16741323/…
user454322

คำตอบ:


78

การหล่อมี 2 ประเภท:

การคัดเลือกโดยนัยเมื่อคุณโยนจากประเภทหนึ่งไปยังประเภทที่กว้างขึ้นซึ่งจะทำโดยอัตโนมัติและไม่มีค่าใช้จ่าย:

String s = "Cast";
Object o = s; // implicit casting

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

Object o = someObject;
String s = (String) o; // explicit casting

ในกรณีที่สองนี้มีค่าใช้จ่ายในรันไทม์เนื่องจากต้องตรวจสอบทั้งสองประเภทและในกรณีที่การแคสต์ไม่สามารถทำได้ JVM จะต้องโยน ClassCastException

นำมาจากJavaWorld: ต้นทุนในการคัดเลือกนักแสดง

การหล่อใช้ในการแปลงระหว่างประเภท - ระหว่างประเภทอ้างอิงโดยเฉพาะสำหรับประเภทของการหล่อที่เราสนใจที่นี่

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

การดำเนินการDowncast (เรียกอีกอย่างว่าการ จำกัด การแปลงในข้อกำหนดภาษา Java) แปลงการอ้างอิงคลาสบรรพบุรุษเป็นการอ้างอิงคลาสย่อย การดำเนินการแคสต์นี้จะสร้างโอเวอร์เฮดการดำเนินการเนื่องจาก Java ต้องการให้ตรวจสอบการส่งที่รันไทม์เพื่อให้แน่ใจว่าถูกต้อง หากอ็อบเจ็กต์ที่อ้างถึงไม่ใช่อินสแตนซ์ของประเภทเป้าหมายสำหรับการร่ายหรือคลาสย่อยของประเภทนั้นการร่ายที่พยายามจะไม่ได้รับอนุญาตและต้องโยน java.lang.ClassCastException


102
บทความ JavaWorld นั้นมีอายุมากกว่า 10 ปีดังนั้นฉันจะใช้คำพูดใด ๆ เกี่ยวกับประสิทธิภาพด้วยเกลือที่ดีที่สุดของคุณเม็ดใหญ่มาก
skaffman

1
@skaffman ในความเป็นจริงฉันจะใช้คำแถลงใด ๆ ที่ทำ (โดยไม่คำนึงถึงประสิทธิภาพที่ไม่เกี่ยวข้อง) ด้วยเกลือเม็ดหนึ่ง
Pacerier

จะเป็นกรณีเดียวกันหรือไม่ถ้าฉันไม่กำหนดวัตถุที่ถูก casted ให้กับการอ้างอิงและเพียงแค่เรียก method กับมัน? like((String)o).someMethodOfCastedClass()
Parth Vishvajit

4
ตอนนี้บทความเกือบ 20 ปีแล้ว และคำตอบก็มีอายุหลายปีด้วย คำถามนี้ต้องการคำตอบที่ทันสมัย
Raslanove

ประเภทดั้งเดิมเป็นอย่างไร? ตัวอย่างเช่นฉันหมายถึง - การคัดเลือกจาก int เป็น short ทำให้เกิดค่าโสหุ้ยที่คล้ายกัน?
luke1985

44

สำหรับการนำ Java ไปใช้งานอย่างเหมาะสม:

วัตถุแต่ละชิ้นมีส่วนหัวที่ประกอบด้วยตัวชี้ไปยังประเภทรันไทม์ (เช่นDoubleหรือStringแต่ไม่สามารถเป็นCharSequenceหรือAbstractList ) สมมติว่าคอมไพลเลอร์รันไทม์ (โดยทั่วไปคือ HotSpot ในกรณีของ Sun) ไม่สามารถกำหนดประเภทแบบคงที่ได้การตรวจสอบบางอย่างจำเป็นต้องดำเนินการโดยรหัสเครื่องที่สร้างขึ้น

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

สำหรับการแคสต์ไปยังประเภทคลาสนั้นเป็นที่ทราบกันดีว่ามีซูเปอร์คลาสกี่คลาสจนกว่าคุณจะตี java.lang.Objectดังนั้นจึงสามารถอ่านประเภทได้โดยใช้ค่าออฟเซ็ตคงที่จากตัวชี้ประเภท (จริงๆแล้วแปดตัวแรกใน HotSpot) อีกครั้งสิ่งนี้คล้ายคลึงกับการอ่านตัวชี้วิธีการสำหรับวิธีเสมือน

จากนั้นค่าที่อ่านได้ก็ต้องการการเปรียบเทียบกับประเภทคงที่ที่คาดไว้ของการร่าย ขึ้นอยู่กับสถาปัตยกรรมชุดคำสั่งคำสั่งอื่นจะต้องแยกสาขา (หรือข้อบกพร่อง) ในสาขาที่ไม่ถูกต้อง ISAs เช่น ARM 32 บิตมีคำสั่งตามเงื่อนไขและอาจสามารถให้เส้นทางเศร้าผ่านเส้นทางแห่งความสุขได้

อินเทอร์เฟซยากขึ้นเนื่องจากการสืบทอดอินเทอร์เฟซหลายรายการ โดยทั่วไปสอง casts ไปยังอินเทอร์เฟซล่าสุดจะถูกแคชไว้ในประเภทรันไทม์ ในช่วงแรก ๆ (กว่าทศวรรษที่แล้ว) อินเทอร์เฟซค่อนข้างช้า แต่ก็ไม่เกี่ยวข้องอีกต่อไป

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


1
น่าสนใจ - นี่หมายความว่าสำหรับคลาสที่ไม่ใช่อินเทอร์เฟซถ้าฉันเขียนคลาสย่อย Superclass sc = (Superclass); ว่าคอมไพเลอร์ (jit ie: load time) จะ "คงที่" ใส่ออฟเซ็ตจาก Object ในแต่ละ Superclass และ Subclass ในส่วนหัว "Class" ของพวกเขาจากนั้นผ่าน add + Compare อย่างง่ายจะสามารถแก้ไขสิ่งต่างๆได้หรือไม่? - ดีและรวดเร็ว :) สำหรับอินเทอร์เฟซฉันจะถือว่าไม่เลวร้ายไปกว่าแฮชแท็กเล็ก ๆ หรือ btree?
peterk

@peterk สำหรับการแคสต์ระหว่างคลาสทั้งอ็อบเจ็กต์แอดเดรสและ "vtbl" (ตารางของพอยน์เตอร์เมธอดบวกตารางลำดับชั้นคลาสแคชอินเทอร์เฟซ ฯลฯ ) จะไม่เปลี่ยนแปลง ดังนั้นนักแสดง [คลาส] จะตรวจสอบประเภทและถ้ามันเข้ากันได้จะไม่มีอะไรเกิดขึ้น
Tom Hawtin - แท

8

ตัวอย่างเช่นสมมติว่าเรามีอาร์เรย์ของ Object [] โดยที่แต่ละองค์ประกอบอาจมีประเภทที่แตกต่างกัน แต่เรารู้แน่นอนอยู่แล้วว่าองค์ประกอบ 0 เป็นสองเท่าองค์ประกอบ 1 คือสตริง (ฉันรู้ว่านี่เป็นการออกแบบที่ผิด แต่สมมติว่าฉันต้องทำสิ่งนี้)

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

ข้อมูลประเภทของ Java ยังคงถูกเก็บไว้ตลอดเวลาหรือไม่ หรือทุกอย่างถูกลืมไปหลังจากการคอมไพล์แล้วและถ้าเราทำ (Double) องค์ประกอบ [0] เราก็จะทำตามตัวชี้และแปลความหมายของ 8 ไบต์นั้นเป็นสองเท่าคืออะไร?

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

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

นอกจากนี้ผู้คนไม่ควรเขียนรหัสแอปพลิเคชันเช่นนั้น


1
สิ่งที่เกี่ยวกับดึกดำบรรพ์? (float) Math.toDegrees(theta)จะมีค่าใช้จ่ายที่สำคัญที่นี่ด้วยหรือไม่?
SD

2
มีค่าใช้จ่ายสำหรับการร่ายแบบดั้งเดิมบางอย่าง ความสำคัญนั้นขึ้นอยู่กับบริบทหรือไม่
Stephen C

6

checkcastการเรียนการสอนรหัสไบต์สำหรับการดำเนินการหล่อที่รันไทม์ที่เรียกว่า คุณสามารถแยกส่วนโค้ด Java โดยใช้javapเพื่อดูคำสั่งที่สร้างขึ้น

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

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpaถูกต้องเนื่องจากColoredPointเป็นคลาสย่อยของ Point แต่pa[0] = new Point()ไม่ถูกต้อง

ซึ่งตรงข้ามกับประเภททั่วไปโดยที่ไม่มีข้อมูลประเภทใดเก็บไว้ที่รันไทม์ คอมไพเลอร์แทรกcheckcastคำแนะนำตามความจำเป็น

ความแตกต่างในการพิมพ์สำหรับประเภททั่วไปและอาร์เรย์นี้ทำให้มักจะไม่เหมาะสมที่จะผสมอาร์เรย์และประเภททั่วไป


2

ในทางทฤษฎีมีการแนะนำค่าโสหุ้ย อย่างไรก็ตาม JVM สมัยใหม่นั้นฉลาด การนำไปใช้งานแต่ละครั้งแตกต่างกัน แต่ก็ไม่สมเหตุสมผลที่จะสันนิษฐานว่าอาจมีการใช้งานที่ JIT ได้ปรับให้เหมาะสมกับการตรวจสอบการหล่อเมื่อสามารถรับประกันได้ว่าจะไม่มีข้อขัดแย้ง สำหรับ JVM ที่เฉพาะเจาะจงเสนอสิ่งนี้ฉันไม่สามารถบอกคุณได้ ฉันต้องยอมรับว่าฉันต้องการทราบข้อมูลเฉพาะของการเพิ่มประสิทธิภาพ JIT ด้วยตัวเอง แต่สิ่งเหล่านี้มีไว้สำหรับวิศวกร JVM ที่ต้องกังวล

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

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