ส่งคืน null เป็น int ที่ได้รับอนุญาตกับผู้ประกอบการที่ประกอบไปด้วยสามส่วน แต่ไม่ใช่ถ้ามีคำสั่ง


186

ลองดูโค้ด Java อย่างง่ายในตัวอย่างต่อไปนี้:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

ในโค้ด Java ที่ง่ายที่สุดนี้temp()เมธอดไม่มีข้อผิดพลาดคอมไพเลอร์แม้ว่าชนิดส่งคืนของฟังก์ชันคือintและเราพยายามคืนค่าnull(ผ่านคำสั่งreturn true ? null : 0;) NullPointerExceptionเมื่อรวบรวมนี้ทำให้เห็นได้ชัดว่าข้อยกเว้นเวลาทำงาน

อย่างไรก็ตามปรากฏว่าสิ่งเดียวกันนั้นผิดถ้าเราเป็นตัวแทนของผู้ประกอบการที่ประกอบไปด้วยifคำสั่ง (ในsame()วิธีการ) ซึ่งจะออกข้อผิดพลาดในการรวบรวมเวลา! ทำไม?


6
นอกจากนี้int foo = (true ? null : 0)และnew Integer(null)ทั้งคู่ก็ปรับตัวที่สองคือรูปแบบที่ชัดเจนของการ autobox
Izkata

2
@Izkata ปัญหาที่นี่คือสำหรับผมที่จะเข้าใจว่าทำไมคอมไพเลอร์จะพยายามที่จะ Autobox nullไปInteger... นั่นจะมีลักษณะเช่นเดียวกับ "คาดเดา" กับผมหรือ "สิ่งที่ทำให้การทำงาน" ...
Marsellus วอลเลซ

1
... อืมฉันคิดว่าฉันมีคำตอบอยู่ที่นั่นในฐานะตัวสร้างจำนวนเต็ม (สิ่งที่เอกสารที่ฉันพบว่าใช้สำหรับการล็อกอัตโนมัติ) ได้รับอนุญาตให้ใช้สตริงเป็นอาร์กิวเมนต์ (ซึ่งอาจเป็นโมฆะ) อย่างไรก็ตามพวกเขายังบอกด้วยว่านวกรรมิกทำหน้าที่เหมือนกันกับวิธีการ parseInt () ซึ่งจะโยน NumberFormatException เมื่อผ่านการเป็นโมฆะ ...
Izkata

3
@Izkata - อาร์กิวเมนต์ String c'tor สำหรับ Integer ไม่ใช่การใช้คำสั่งอัตโนมัติ สตริงไม่สามารถ autoboxed ไปยัง Integer ได้ (ฟังก์ชั่นInteger foo() { return "1"; }จะไม่รวบรวม.)
เท็ด Hopp

5
เจ๋งได้เรียนรู้สิ่งใหม่เกี่ยวกับผู้ประกอบการที่สาม!
oksayt

คำตอบ:


118

คอมไพเลอร์ตีความnullว่าเป็นโมฆะอ้างอิงกับIntegerใช้กฎการ autoboxing / unboxing สำหรับผู้ประกอบการตามเงื่อนไข (ตามที่อธิบายไว้ในJava Language Specification, 15.25 ) และย้ายอย่างมีความสุข สิ่งนี้จะสร้างNullPointerExceptionเวลารันไทม์ซึ่งคุณสามารถยืนยันได้โดยลองทำดู


ระบุลิงก์ไปยังข้อกำหนดภาษา Java ที่คุณโพสต์คุณคิดว่าประเด็นใดที่จะถูกดำเนินการในกรณีของคำถามข้างต้น คนสุดท้าย (ตั้งแต่ฉันยังคงพยายามที่จะเข้าใจcapture conversionและlub(T1,T2))? นอกจากนี้มันเป็นไปได้จริง ๆ ที่จะใช้มวยเป็นค่าว่างหรือไม่ สิ่งนี้จะไม่เป็น "การคาดเดา" หรือไม่?
Marsellus Wallace

´@ Gevorg ตัวชี้ null เป็นตัวชี้ที่ถูกต้องสำหรับทุกวัตถุที่เป็นไปได้ดังนั้นจึงไม่มีอะไรเลวร้ายเกิดขึ้นได้ คอมไพเลอร์เพิ่งจะสันนิษฐานว่าโมฆะนั้นเป็นจำนวนเต็มซึ่งจะทำให้ออโต้บ็อกซ์เป็น int
Voo

1
@Gevorg - ดูความคิดเห็นของ nowaq และตอบกลับโพสต์ของเขา ฉันคิดว่าเขาเลือกประโยคที่ถูกต้อง lub(T1,T2)เป็นประเภทอ้างอิงที่เฉพาะเจาะจงมากที่สุดโดยทั่วไปในลำดับชั้นประเภทของ T1 และ T2 (พวกเขาทั้งสองมีส่วนแบ่งที่วัตถุน้อยจึงมีอยู่เสมอเป็นชนิดการอ้างอิงเฉพาะเจาะจงมากที่สุด.)
เท็ด Hopp

8
@Gevorg - nullไม่ได้บรรจุอยู่ใน Integer แต่จะถูกตีความเป็นการอ้างอิงถึง Integer (การอ้างอิง Null แต่นั่นไม่ใช่ปัญหา) ไม่มีวัตถุจำนวนเต็มถูกสร้างขึ้นจากค่า Null ดังนั้นจึงไม่มีเหตุผลสำหรับการ NumberFormatException
Ted Hopp

1
@Gevorg - หากคุณดูกฎสำหรับการแปลงมวยและนำไปใช้กับnull(ซึ่งไม่ใช่ประเภทตัวเลขดั้งเดิม) ประโยคที่เกี่ยวข้องคือ "ถ้าpเป็นค่าของประเภทอื่นการแปลงมวยจะเทียบเท่ากับการแปลงตัวตน " ดังนั้นการแปลงมวยnullเป็นIntegerผลตอบแทนnullโดยไม่ต้องเรียกใช้ตัวIntegerสร้างใด ๆ
Ted Hopp

40

ผมคิดว่าตีความเรียบเรียง Java true ? null : 0เป็นIntegerแสดงออกซึ่งสามารถแปลงโดยปริยายอาจจะให้intNullPointerException

สำหรับกรณีที่สองนิพจน์nullเป็นประเภท null พิเศษดูดังนั้นรหัสreturn nullทำให้ไม่ตรงกันประเภท


2
ฉันคิดว่ามันเกี่ยวข้องกับการชกมวยอัตโนมัติหรือไม่? สมมุติว่าผลตอบแทนแรกจะไม่รวบรวมก่อน Java 5 ใช่มั้ย
Michael McGowan

@Michael ที่ดูเหมือนจะเป็นกรณีนี้หากคุณตั้งค่าระดับความสอดคล้องของ Eclipse เป็น pre-5
Jonathon Faust

@Michael: ดูเหมือนว่ามวยอัตโนมัติ (ฉันค่อนข้างใหม่กับ Java และไม่สามารถสร้างคำสั่งที่ชัดเจนมากขึ้น - ขอโทษ)

1
@Vlad คอมไพเลอร์จะจบการตีความtrue ? null : 0เป็นIntegerอย่างไร โดย autoboxing 0ก่อน
Marsellus Wallace

1
@Gevorg: ดูที่นี่ : มิฉะนั้นตัวถูกดำเนินการที่สองและสามเป็นประเภท S1 และ S2 ตามลำดับ ให้ T1 เป็นประเภทที่เป็นผลมาจากการใช้การแปลงมวยเป็น S1 และให้ T2 เป็นประเภทที่เป็นผลมาจากการใช้การแปลงมวยเป็น S2 และข้อความต่อไปนี้

32

ที่จริงทั้งหมดที่อธิบายไว้ในข้อมูลจำเพาะภาษา Java

ชนิดของนิพจน์เงื่อนไขถูกกำหนดดังนี้:

  • หากตัวถูกดำเนินการที่สองและที่สามมีประเภทเดียวกัน (ซึ่งอาจเป็นประเภทที่เป็นโมฆะ) แสดงว่าเป็นประเภทของนิพจน์เงื่อนไข

ดังนั้น "โมฆะ" ในประเภทของคุณ(true ? null : 0)จะได้รับ int แล้ว autoboxed เพื่อจำนวนเต็ม

ลองสิ่งนี้เพื่อยืนยันสิ่งนี้(true ? null : null)และคุณจะได้รับข้อผิดพลาดของคอมไพเลอร์


3
แต่ประโยคของกฎนั้นใช้ไม่ได้: ตัวถูกดำเนินการที่สองและสามไม่มีประเภทเดียวกัน
Ted Hopp

1
จากนั้นคำตอบน่าจะอยู่ในคำสั่งต่อไปนี้:> มิฉะนั้นตัวถูกดำเนินการที่สองและสามเป็นประเภท S1 และ S2 ตามลำดับ ให้ T1 เป็นประเภทที่เป็นผลมาจากการใช้การแปลงมวยเป็น S1 และให้ T2 เป็นประเภทที่เป็นผลมาจากการใช้การแปลงมวยเป็น S2 ชนิดของนิพจน์เงื่อนไขคือผลลัพธ์ของการใช้การแปลงการจับภาพ (§5.1.10) กับ lub (T1, T2) (§15.12.2.7)
nowaq

ฉันคิดว่านั่นเป็นประโยคที่เกี่ยวข้อง จากนั้นจะพยายามใช้การ unbox อัตโนมัติเพื่อส่งคืนintค่าจากฟังก์ชันซึ่งเป็นสาเหตุของ NPE
Ted Hopp

@nowaq ฉันก็คิดเช่นนี้เช่นกัน แต่ถ้าคุณพยายามที่จะชัดเจนกล่องnullไปIntegerกับnew Integer(null);"Let T1 เป็นชนิดที่เป็นผลมาจากการใช้การแปลงมวย S1 ..." คุณจะได้รับNumberFormatExceptionและไม่ได้เป็นกรณี ...
Marsellus วอลเลซ

@Gevorg ฉันคิดว่าเนื่องจากมีข้อยกเว้นเกิดขึ้นเมื่อทำการชกมวยเราไม่ได้รับผลใด ๆ ที่นี่ คอมไพเลอร์มีหน้าที่เพียงสร้างรหัสที่ตามหลังคำจำกัดความ - เราเพิ่งได้รับข้อยกเว้นก่อนที่เราจะเสร็จสิ้น
Voo

25

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

สำหรับโอเปอเรเตอร์ที่มีเงื่อนไขข้อกำหนดภาษา Java §15.25“ ผู้ปฏิบัติตามเงื่อนไข? :” จะตอบคำถามนี้อย่างดีในกฎสำหรับวิธีการใช้การแปลงประเภท:

  • หากตัวถูกดำเนินการที่สองและที่สามมีประเภทเดียวกัน (ซึ่งอาจเป็นประเภทที่เป็นโมฆะ) แสดงว่าเป็นประเภทของนิพจน์เงื่อนไข

    ใช้ไม่ได้เพราะไม่ได้เป็นnullint

  • หากหนึ่งในตัวถูกดำเนินการที่สองและที่สามเป็นประเภทบูลีนและประเภทของตัวถูกอื่นเป็นประเภทบูลีนแล้วประเภทของการแสดงออกตามเงื่อนไขเป็นบูลีน

    ใช้ไม่ได้เพราะไม่nullว่ามิได้intเป็นหรือbooleanBoolean

  • หากหนึ่งในตัวถูกดำเนินการที่สองและที่สามเป็นประเภท null และประเภทของตัวถูกดำเนินการเป็นประเภทการอ้างอิงประเภทของการแสดงออกตามเงื่อนไขเป็นประเภทการอ้างอิงนั้น

    ไม่ใช้เนื่องจากnullเป็นประเภท null แต่intไม่ใช่ประเภทอ้างอิง

  • มิฉะนั้นหากตัวถูกดำเนินการตัวที่สองและสามมีประเภทที่แปลงได้ (§5.1.8) เป็นประเภทตัวเลขก็มีหลายกรณี: […] การ

    ใช้งาน: nullถือว่าเป็นประเภทที่แปลงได้เป็นประเภทตัวเลขและกำหนดไว้ใน§5.1 8“Unboxing แปลง” NullPointerExceptionที่จะโยน

หาก0autoboxed ไปIntegerแล้วคอมไพเลอร์กำลังดำเนินการกรณีสุดท้ายของ "กฎประกอบการประกอบไปด้วยสาม" ตามที่อธิบายไว้ใน Java Language Specification ถ้านั่นเป็นเรื่องจริงมันยากสำหรับฉันที่จะเชื่อว่ามันจะข้ามไปยังกรณีที่ 3 ของกฎเดียวกันกับที่มีโมฆะและประเภทการอ้างอิงที่ทำให้ค่าตอบแทนของผู้ประกอบการที่เป็นประเภทอ้างอิง (จำนวนเต็ม) .. .
Marsellus Wallace

@Gevorg - ทำไมมันยากที่จะเชื่อว่าผู้ประกอบการที่สามจะกลับมาInteger? นั่นคือสิ่งที่เกิดขึ้น NPE กำลังถูกสร้างขึ้นโดยพยายามที่จะแกะกล่องค่าการแสดงออกเพื่อส่งกลับintจากฟังก์ชั่น เปลี่ยนฟังก์ชั่นเพื่อคืนค่าIntegerและมันจะกลับมาnullโดยไม่มีปัญหา
Ted Hopp

2
@TedHopp: Gevorg ตอบสนองต่อการแก้ไขคำตอบของฉันก่อนหน้านี้ซึ่งไม่ถูกต้อง คุณควรละเว้นความคลาดเคลื่อน
Jon Purdy

@JonPurdy "ชนิดหนึ่งถูกกล่าวว่าสามารถแปลงเป็นประเภทตัวเลขได้หากเป็นประเภทตัวเลขหรือเป็นประเภทอ้างอิงที่อาจถูกแปลงเป็นประเภทตัวเลขโดยการแปลงแบบกล่อง unboxing" และฉันไม่คิดว่าnullตกอยู่ในประเภทนี้ . นอกจากนี้เราจะเข้าสู่ขั้นตอน "มิฉะนั้นจะใช้การเลื่อนระดับเป็นตัวเลขไบนารี (§5.6.2) ... โปรดทราบว่าการเลื่อนขั้นเป็นตัวเลขไบนารีจะทำการแปลงที่ไม่มีกล่อง (§5.1.8) ... " เพื่อกำหนดประเภทการส่งคืน แต่การแปลงแบบไม่มีกล่องจะสร้าง NPE และสิ่งนี้จะเกิดขึ้นเฉพาะขณะใช้งานจริงและไม่เกิดขึ้นในขณะที่พยายามกำหนดประเภทผู้ประกอบการ ฉันยังสับสนอยู่ ..
Marsellus Wallace

@Gevorg: การ Unboxing เกิดขึ้นขณะใช้งานจริง nullได้รับการปฏิบัติราวกับว่ามันมีประเภทintแต่จริง ๆ แล้วเทียบเท่ากับthrow new NullPointerException()ที่ทั้งหมด
Jon Purdy

11

สิ่งแรกที่ต้องจำไว้ก็คือผู้ประกอบการที่ประกอบไปด้วย Java มี "ประเภท" และนี่คือสิ่งที่คอมไพเลอร์จะตรวจสอบและพิจารณาไม่ว่าสิ่งที่ประเภทที่แท้จริง / จริงของพารามิเตอร์ที่สองหรือสามคือ ขึ้นอยู่กับปัจจัยหลายประการประเภทผู้ประกอบการที่ถูกกำหนดในรูปแบบที่แตกต่างกันตามที่แสดงในJava Language Specification 15.26

ในคำถามข้างต้นเราควรพิจารณากรณีสุดท้าย:

มิฉะนั้นตัวถูกดำเนินการที่สองและสามเป็นประเภทS1และS2ตามลำดับ ให้T1เป็นชนิดที่เป็นผลมาจากการใช้การแปลงมวยS1และให้T2เป็นชนิดที่เป็นผลมาจากการใช้การแปลงมวยS2 ชนิดของนิพจน์เงื่อนไขคือผลลัพธ์ของการใช้การแปลงการจับภาพ (.15.1.10) กับlub (T1, T2) (§15.12.2.7)

นี่คือไกลโดยกรณีที่ซับซ้อนมากที่สุดเมื่อคุณดูที่การใช้การแปลงจับ (§5.1.10)และส่วนใหญ่ของทั้งหมดในหลุบ (T1, T2)

ในภาษาอังกฤษธรรมดาและหลังจากการทำให้เข้าใจง่ายสุดขีดเราสามารถอธิบายขั้นตอนการคำนวณ "Least Common Superclass" (ใช่ลองคิดถึง LCM) ของพารามิเตอร์ที่สองและสาม สิ่งนี้จะทำให้เราเป็นผู้ประกอบการ "พิมพ์" อีกครั้งสิ่งที่ฉันเพิ่งพูดคือการทำให้เข้าใจง่ายมาก (พิจารณาคลาสที่ใช้อินเทอร์เฟซทั่วไปหลายตัว)

ตัวอย่างเช่นหากคุณลองทำสิ่งต่อไปนี้:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

คุณจะสังเกตเห็นว่าประเภทของการแสดงออกตามเงื่อนไขนั้นเป็นjava.util.Dateเพราะ "Least Common Superclass" สำหรับTimestamp/ Timeคู่

เนื่องจากnullสามารถถูกล็อกอัตโนมัติกับสิ่งใด ๆ "Least Common Superclass" เป็นIntegerคลาสและนี่จะเป็นชนิดส่งคืนของนิพจน์เงื่อนไข (ตัวดำเนินการที่ประกอบไปด้วย) ด้านบน ค่าส่งคืนจะเป็นตัวชี้ null ของประเภทIntegerและนั่นคือสิ่งที่จะถูกส่งกลับโดยผู้ประกอบการที่ประกอบไปด้วย

ณ รันไทม์เมื่อ Java Virtual Machine ยกเลิกการIntegerทำเครื่องหมาย a NullPointerExceptionจะถูกส่งออกไป สิ่งนี้เกิดขึ้นเนื่องจาก JVM พยายามเรียกใช้ฟังก์ชันnull.intValue()ซึ่งnullเป็นผลมาจากการล็อคอัตโนมัติ

ในความคิดของฉัน (และเนื่องจากความคิดเห็นของฉันไม่ได้อยู่ในข้อกำหนดภาษา Java หลายคนจะพบว่ามันผิดอยู่ดี) คอมไพเลอร์ทำงานได้ไม่ดีในการประเมินการแสดงออกในคำถามของคุณ ระบุว่าคุณเขียนtrue ? param1 : param2คอมไพเลอร์ควรกำหนดทันทีว่าพารามิเตอร์แรกnull- จะถูกส่งกลับและควรสร้างข้อผิดพลาดคอมไพเลอร์ นี้จะค่อนข้างคล้ายกับเมื่อคุณเขียนและเรียบเรียงบ่นเกี่ยวกับรหัสใต้ห่วงและธงด้วยwhile(true){} etc...Unreachable Statements

กรณีที่สองของคุณค่อนข้างตรงไปตรงมาและคำตอบนี้ยาวเกินไปแล้ว ... ;)

แก้ไข:

หลังจากการวิเคราะห์อื่นฉันเชื่อว่าฉันผิดที่จะบอกว่าnullค่าสามารถบรรจุ / autoboxed เพื่ออะไร พูดคุยเกี่ยวกับระดับจำนวนเต็มมวยอย่างชัดเจนประกอบด้วยในการอัญเชิญnew Integer(...)คอนสตรัคหรืออาจจะเป็นInteger.valueOf(int i);(ผมพบว่ารุ่นนี้อยู่ที่ไหนสักแห่ง) อดีตจะขว้างNumberFormatException(และสิ่งนี้ไม่เกิดขึ้น) ในขณะที่สองจะไม่สมเหตุสมผลเนื่องจากintไม่สามารถnull...


1
nullในรหัสต้นฉบับของ OP ไม่ได้บรรจุอยู่ในกล่อง วิธีการทำงานคือคอมไพเลอร์จะถือว่าการnullอ้างอิงถึงจำนวนเต็ม การใช้กฎสำหรับประเภทนิพจน์สามส่วนจะตัดสินว่านิพจน์ทั้งหมดเป็นนิพจน์จำนวนเต็ม จากนั้นจะสร้างรหัสเพื่อ autobox 1(ในกรณีที่เงื่อนไขประเมินfalse) ในระหว่างการดำเนินสภาพประเมินเพื่อประเมินการแสดงออกไปtrue nullเมื่อพยายามคืนค่าintจากฟังก์ชั่นnullจะไม่มีกล่อง จากนั้นจะโยน NPE (คอมไพเลอร์อาจปรับให้เหมาะกับสิ่งนี้มากที่สุด)
Ted Hopp

4

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


2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}

0

เกี่ยวกับสิ่งนี้:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

ผลลัพธ์เป็นจริงจริง

Eclipse รหัสสี 1 ในการแสดงออกตามเงื่อนไขเป็น autoboxed

ฉันเดาว่าคอมไพเลอร์จะเห็นประเภทผลตอบแทนของการแสดงออกเป็นวัตถุ

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