ตัวดำเนินการที่เกี่ยวข้องกับ Java เทียบกับ if / else ในความเข้ากันได้ <JDK8


113

เมื่อเร็ว ๆ นี้ฉันกำลังอ่านซอร์สโค้ดของ Spring Framework มีบางอย่างที่ฉันไม่เข้าใจอยู่ที่นี่:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

org.springframework.core.MethodParameterวิธีการนี้เป็นสมาชิกของชั้นเรียน รหัสนั้นเข้าใจง่ายในขณะที่ความคิดเห็นนั้นยาก

หมายเหตุ: ไม่มีนิพจน์ด้านท้ายเพื่อรักษาความเข้ากันได้ของ JDK <8 แม้ว่าจะใช้คอมไพเลอร์ JDK 8 (อาจเลือกjava.lang.reflect.Executableเป็นประเภททั่วไปโดยที่คลาสพื้นฐานใหม่นั้นไม่มีใน JDK รุ่นเก่า)

อะไรคือความแตกต่างระหว่างการใช้นิพจน์ ternary และการใช้if...else...โครงสร้างในบริบทนี้?

คำตอบ:


103

เมื่อคุณคิดถึงประเภทของตัวถูกดำเนินการปัญหาจะชัดเจนมากขึ้น:

this.method != null ? this.method : this.constructor

ได้เป็นชนิดชนิดที่พบมากที่สุดความเชี่ยวชาญของทั้งสองตัวถูกดำเนินการเช่นชนิดที่พบมากที่สุดที่จะเชี่ยวชาญทั้งในและthis.methodthis.constructor

ใน Java 7 นี้อยู่java.lang.reflect.Memberแต่ห้องสมุดชั้น Java 8 แนะนำรูปแบบใหม่ที่มีความเชี่ยวชาญมากขึ้นกว่าทั่วไปjava.lang.reflect.Executable Memberดังนั้นกับห้องสมุดชั้น Java 8 ประเภทผลของการแสดงออก ternary เป็นมากกว่าExecutableMember

คอมไพลเลอร์ Java 8 บางเวอร์ชัน (ก่อนวางจำหน่าย) ดูเหมือนจะมีการอ้างอิงอย่างชัดเจนไปยังExecutableโค้ดที่สร้างขึ้นภายในเมื่อรวบรวมตัวดำเนินการ ternary สิ่งนี้จะทริกเกอร์การโหลดคลาสและทำให้เป็นClassNotFoundExceptionรันไทม์เมื่อรันด้วยไลบรารีคลาส <JDK 8 เนื่องจากExecutableมีเฉพาะสำหรับ JDK ≥ 8

ตามที่ Tagir Valeev ระบุไว้ในคำตอบนี้จริงๆแล้วนี่เป็นข้อบกพร่องใน JDK 8 เวอร์ชันก่อนวางจำหน่ายและได้รับการแก้ไขแล้วดังนั้นทั้งif-elseวิธีแก้ปัญหาและความคิดเห็นเชิงอธิบายจึงล้าสมัยแล้ว

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


1
แล้วโค้ดที่คอมไพล์ด้วย JDK 1.8 จะรันบน JDK 1.7 ได้อย่างไร ฉันรู้ว่าโค้ดที่คอมไพล์ด้วย JDK เวอร์ชันที่ต่ำกว่าสามารถทำงานบน JDK เวอร์ชันที่สูงกว่าได้โดยไม่มีปัญหาในทางกลับกันดีไหม
jddxf

1
@jddxf ทุกอย่างเรียบร้อยดีตราบเท่าที่คุณระบุเวอร์ชันไฟล์คลาสที่เหมาะสมและอย่าใช้ฟังก์ชันใด ๆ ที่ไม่มีในเวอร์ชันที่ใหม่กว่า ปัญหาจะเกิดขึ้นอย่างไรก็ตามหากการใช้งานดังกล่าวเกิดขึ้นโดยปริยายเช่นในกรณีนี้
dhke

13
@jddxf, use -source / -target javac options
Tagir Valeev

1
ขอบคุณทุกคนโดยเฉพาะ dhke และ Tagir Valeev ที่ให้คำอธิบายอย่างละเอียด
jddxf

30

สิ่งนี้ถูกนำมาใช้ในการกระทำที่ค่อนข้างเก่าเมื่อวันที่ 3 พฤษภาคม 2013 เกือบหนึ่งปีก่อนการเปิดตัว JDK-8 อย่างเป็นทางการ คอมไพเลอร์อยู่ระหว่างการพัฒนาอย่างหนักในช่วงเวลานั้นดังนั้นปัญหาความเข้ากันได้ดังกล่าวอาจเกิดขึ้นได้ ฉันเดาว่าทีม Spring เพิ่งทดสอบ JDK-8 build และพยายามแก้ไขปัญหาแม้ว่าจะเป็นปัญหาเกี่ยวกับคอมไพเลอร์ก็ตาม โดยการเปิดตัวอย่างเป็นทางการของ JDK-8 สิ่งนี้ไม่เกี่ยวข้อง ขณะนี้ตัวดำเนินการ ternary ในรหัสนี้ทำงานได้ดีตามที่คาดไว้ (ไม่มีการอ้างอิงถึงExecutableคลาสในไฟล์. class ที่คอมไพล์แล้ว)

ขณะนี้สิ่งที่คล้ายกันปรากฏใน JDK-9: โค้ดบางตัวที่สามารถคอมไพล์ได้ดีใน JDK-8 ล้มเหลวด้วย JDK-9 javac ฉันเดาว่าปัญหาดังกล่าวส่วนใหญ่จะได้รับการแก้ไขจนถึงการเปิดตัว


2
+1 นี่เป็นบั๊กในคอมไพเลอร์ยุคแรกหรือไม่? พฤติกรรมดังกล่าวถูกอ้างถึงExecutableในกรณีที่ละเมิดข้อกำหนดบางประการหรือไม่? หรือเป็นเพียงการที่ Oracle ตระหนักว่าพวกเขาสามารถเปลี่ยนพฤติกรรมนี้ในลักษณะที่ยังคงเป็นไปตามข้อกำหนดและไม่ทำลายความเข้ากันได้แบบย้อนกลับ
ruakh

2
@ruakh ฉันคิดว่ามันเป็นจุดบกพร่อง ใน bytecode (ไม่ว่าจะใน Java-8 หรือรุ่นก่อนหน้า) ไม่จำเป็นอย่างยิ่งที่จะต้องแคสต์อย่างชัดเจนเพื่อExecutableพิมพ์ระหว่าง ใน Java-8 แนวคิดของการอนุมานประเภทนิพจน์เปลี่ยนไปอย่างมากและส่วนนี้ถูกเขียนใหม่ทั้งหมดดังนั้นจึงไม่น่าแปลกใจที่การใช้งานในช่วงต้นจะมีข้อบกพร่อง
Tagir Valeev

7

ความแตกต่างที่สำคัญคือif elseบล็อกเป็นคำสั่งในขณะที่เทอร์นารี (มักเรียกว่าตัวดำเนินการเงื่อนไขใน Java) เป็นนิพจน์การแสดงออก

คำสั่งสามารถทำสิ่งต่างๆเช่นreturnการโทรในบางส่วนของเส้นทางการควบคุม แสดงออกสามารถนำมาใช้ในการกำหนด:

int n = condition ? 3 : 2;

ดังนั้นสองนิพจน์ใน ternary หลังเงื่อนไขจึงต้องบังคับให้เป็นประเภทเดียวกัน สิ่งนี้อาจทำให้เกิดเอฟเฟกต์แปลก ๆ ใน Java โดยเฉพาะกับการชกมวยอัตโนมัติและการแคสต์อ้างอิงอัตโนมัติ - นี่คือความคิดเห็นในโค้ดที่โพสต์ของคุณอ้างถึง การบีบบังคับของนิพจน์ในกรณีของคุณจะเป็นjava.lang.reflect.Executableประเภทหนึ่ง (เนื่องจากเป็นประเภทที่เชี่ยวชาญที่สุด ) และไม่มีอยู่ใน Java เวอร์ชันเก่า

ในทางโวหารคุณควรใช้if elseบล็อกถ้ารหัสเป็นเหมือนคำสั่งและส่วนท้ายถ้าเป็นนิพจน์

แน่นอนคุณสามารถทำให้if elseบล็อกมีพฤติกรรมเหมือนนิพจน์ได้หากคุณใช้ฟังก์ชันแลมบ์ดา


6

ชนิดค่าที่ส่งคืนในนิพจน์ ternary ได้รับผลกระทบจากคลาสพาเรนต์ซึ่งเปลี่ยนแปลงตามที่อธิบายไว้ใน Java 8

ยากที่จะดูว่าทำไมไม่สามารถเขียนนักแสดงได้

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