1 คะแนน โดย GN⁺ 2024-08-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Java กำลังเตรียม ฟีเจอร์ภาษาแบบพรีวิว ที่เพิ่ม มาร์กเกอร์ nullness ให้กับชนิด โดย Foo! ปฏิเสธ null และ Foo? อนุญาตโดยเจตนา
  • Foo ที่ไม่มีมาร์กเกอร์จะยังมี nullness เป็น ไม่ได้ระบุ เพื่อคงความเข้ากันได้กับโค้ดเดิม และการแปลงระหว่างชนิดที่มี nullness ต่างกันอาจมาพร้อมคำเตือนหรือการตรวจสอบตอนรันไทม์
  • ฟิลด์และอาร์เรย์ที่ปฏิเสธ null ไม่สามารถใช้ค่าเริ่มต้น null ได้ ดังนั้นฟิลด์ของอินสแตนซ์ต้องถูกกำหนดค่าอย่างแน่นอนก่อนเรียก super(...) และอาร์เรย์ต้องมี ค่าเริ่มต้น ของคอมโพเนนต์
  • เมื่อ null ถูกแคบลงเป็น Foo! จะเกิด NullPointerException และในเส้นทางการเก็บลงอาร์เรย์/ฟิลด์ จะป้องกันการปนเปื้อนด้วย ArrayStoreException และ FieldStoreException
  • นี่เป็นรากฐานที่ทำให้การปรับแต่งประสิทธิภาพอย่าง การทำให้ value class แบนราบ ของ Valhalla สามารถเชื่อถือการตัด null ออกได้ โดยการนำไปใช้กับไลบรารีมาตรฐานหรือการตีความโค้ดเดิมใหม่อัตโนมัติยังไม่ใช่เป้าหมายในตอนนี้

ฟีเจอร์พรีวิวสำหรับระบุ nullness ในชนิดของ Java

  • เพิ่ม มาร์กเกอร์ nullness ให้ชนิดของ Java เพื่อแสดงว่าชุดค่าของชนิดนั้นมี null รวมอยู่หรือไม่
  • Foo! คือชนิด null-restricted ซึ่งตัด null ออกจากชุดค่า
  • Foo? คือชนิด nullable ซึ่งตั้งใจรวม null ไว้ในชุดค่า
  • Foo ที่ไม่มีมาร์กเกอร์มี nullness เป็น unspecified โดยอาจเกิด null ได้ แต่ไม่อาจรู้ได้ว่าการมีอยู่ของมันเป็นความตั้งใจหรือไม่
  • ฟีเจอร์นี้เป็น ฟีเจอร์พรีวิว ที่เปิดใช้ด้วยแฟล็กคอมไพล์และรันไทม์ --enable-preview

เป้าหมายและสิ่งที่ไม่ใช่เป้าหมาย

  • แสดงว่าชนิดอ้างอิงของ Java คาดหวัง null หรือไม่ และให้คำเตือนกับการตรวจสอบสำหรับการแปลงระหว่างชนิดที่มี nullness แตกต่างกัน
  • เข้ากันได้ กับโค้ด Java เดิม และต้องสามารถนำมาใช้แบบค่อยเป็นค่อยไปโดยไม่สร้างความไม่เข้ากันทั้งในระดับซอร์สหรือไบนารี
  • ตัวแปรของชนิดที่ปฏิเสธ null ต้องถูกเริ่มต้นก่อนถูกอ่านครั้งแรก และการปฏิเสธ null จะถูกบังคับตอนรันไทม์แม้ในคลาสที่คอมไพล์แยกกัน
  • มอบเมตาดาต้าและการรับประกันความถูกต้องครบถ้วน เพื่อให้การปรับแต่งรันไทม์อย่างการทำให้ value class ของ Valhalla แบนราบสามารถเชื่อถือชนิดที่จำกัด null ได้
  • รายการที่ยังไม่ใช่เป้าหมายในตอนนี้มีดังนี้
    • ไม่ตีความโค้ดเดิมใหม่โดยอัตโนมัติ
    • ไม่ทำให้ null ที่เป็นไปได้ทั้งหมดกลายเป็นข้อผิดพลาดตอนคอมไพล์
    • ไม่เพิ่มรูปแบบ nullable ให้ชนิดพื้นฐานอย่าง int
    • ไม่ใช้ส่วนขยายภาษาเหล่านี้กับไลบรารีมาตรฐานในขั้นตอนปัจจุบัน

ทำไมจึงจำเป็น

  • ตัวแปร String ของ Java สามารถเก็บการอ้างอิงออบเจ็กต์ String หรือ null ได้ แต่ในระดับภาษาไม่มีวิธีแสดงว่าตั้งใจให้เป็นแบบใด
  • โปรแกรมจำนวนมากสมมติว่าไม่มี null แต่ต้องมีงานเพิ่มเติมเพื่อบังคับใช้เรื่องนี้ให้สอดคล้องกันทั้งในสเปก Javadoc และโค้ดอิมพลีเมนต์
  • เมื่อความคาดหวังนี้พัง ค่า null อาจไหลอยู่ในโค้ดอิมพลีเมนต์แล้วไปทำให้เกิดข้อยกเว้น ณ จุดที่อยู่ไกลจากบั๊กต้นเหตุ
  • หากนักพัฒนาแสดงเจตนาว่าปฏิเสธหรืออนุญาต null เป็นส่วนหนึ่งของชนิด ฟีดแบ็กตอนคอมไพล์และการตรวจสอบตอนรันไทม์จะช่วยค้นหา null ที่ไม่คาดคิดได้เร็วขึ้น
  • ใน Valhalla ตัวแปรชนิด value class อาจถูกปรับแต่งเป็น การแทนค่าแบบแบนราบ ของค่าได้ แต่ถ้าต้องมีบิตเพิ่มเติมเพื่อเข้ารหัส null ก็อาจเพิ่มการใช้หน่วยความจำหรือทำให้ปรับแต่งพื้นที่จัดเก็บไม่ได้
  • ใน Amber ค่า nullness ของผู้สมัครสำหรับ pattern matching อาจมีผลต่อการตัดสิน exhaustiveness ของ switch และค่า nullness ของ type pattern อาจมีผลต่อว่าจะจับคู่กับ null หรือไม่

ไวยากรณ์มาร์กเกอร์ nullness และโครงสร้างชนิด

  • nullness ถือเป็นส่วนหนึ่งโดยเนื้อแท้ของชนิด และ Foo? กับ Foo เป็น คนละชนิด กันเพราะมี nullness ต่างกัน
  • ทั้งชนิดอาร์เรย์และชนิดคอมโพเนนต์ของอาร์เรย์สามารถมีมาร์กเกอร์ nullness ได้
    • Foo?[]! คือชนิดที่ตัวอาร์เรย์เองเป็น null-restricted และคอมโพเนนต์เป็น Foo แบบ nullable
    • ในอาร์เรย์หลายมิติ สามารถใส่มาร์กเกอร์หลังวงเล็บเหลี่ยมแต่ละคู่ได้ และตามธรรมเนียมจะตีความจากซ้ายไปขวา จากด้านนอกไปด้านใน
  • ชนิดแบบ parameterized และอาร์กิวเมนต์ชนิดก็สามารถมีมาร์กเกอร์ nullness ได้
    • Predicate!<Foo?> คือ Predicate แบบ null-restricted และอาร์กิวเมนต์ชนิดเป็น Foo แบบ nullable
  • หากต้องการแสดงชนิดที่จำกัด null หรืออนุญาต null ซอร์สต้องมี ! หรือ ? ปรากฏอย่าง ชัดเจน
  • ในอนาคตอาจพิจารณาวิธีที่ตีความชนิดทั้งหมดในคลาสหรือหน่วยคอมไพล์ให้เป็น null-restricted โดยปริยาย และใช้ ? เป็นข้อยกเว้นเท่านั้น แต่รายละเอียดเป็นงานแยกต่างหาก

กฎการเริ่มต้นฟิลด์และอาร์เรย์

  • ใน Java เดิม ค่าเริ่มต้นของฟิลด์ชนิดอ้างอิงและคอมโพเนนต์อาร์เรย์คือ null แต่ไม่เหมาะสำหรับค่าเริ่มต้นของฟิลด์หรือคอมโพเนนต์อาร์เรย์แบบ null-restricted
  • ฟิลด์และอาร์เรย์แบบ null-restricted ต้องถูกโปรแกรมเริ่มต้นเสมอก่อนถูกอ่าน
  • หากฟิลด์อินสแตนซ์แบบ null-restricted ไม่มี initializer จะต้องถูก กำหนดค่าอย่างแน่นอน ก่อนการเรียก super(...) แบบชัดแจ้งหรือโดยนัยในแต่ละคอนสตรักเตอร์
    • Flexible Constructor Bodies JEP ทำให้เขียนโค้ดเริ่มต้นที่จำเป็นไว้ตอนต้นของคอนสตรักเตอร์ได้
    • ใน early construction context นี้ จะไม่อนุญาตการกระทำที่มีความเสี่ยง เช่น การอ้างถึง this หรือการอ่านฟิลด์ที่ยังไม่ได้เริ่มต้น
  • หากฟิลด์อินสแตนซ์แบบ null-restricted มี initializer จะถูกเรียกใช้ตอนเริ่มของแต่ละคอนสตรักเตอร์ ก่อนการเรียก super(...)
    • คอนสตรักเตอร์ที่เรียก this(...) เป็นกรณีพิเศษที่ไม่เรียกใช้ initializer เช่นเดียวกับกฎเดิม
  • ฟิลด์สแตติกแบบ null-restricted ต้องถูกกำหนดค่าอย่างแน่นอนก่อนที่ static initializer และบล็อกเริ่มต้นทั้งหมดของคลาสจะจบลง
    • หากคลาสอื่นพยายามอ่านฟิลด์นั้นระหว่างการเริ่มต้นคลาส การตรวจสอบตอนรันไทม์จะตรวจพบการอ่านก่อนเวลาและโยนข้อยกเว้น
  • อาร์เรย์ที่มีชนิดคอมโพเนนต์แบบ null-restricted ต้องระบุ ค่าเริ่มต้น ของแต่ละคอมโพเนนต์ในนิพจน์สร้างอาร์เรย์
    • สามารถลิสต์ค่าทั้งหมดด้วย array initializer ได้
    • ไวยากรณ์ย่อแบบใหม่ก็เป็นไปได้เช่นกัน แต่ไวยากรณ์ยัง TBD

nullness ของนิพจน์และการแปลง nullness

  • คอมไพเลอร์ Java จะกำหนด nullness ของนิพจน์ทั้งหมดในกระบวนการตรวจสอบชนิด
  • nullness ของการอ้างอิงตัวแปรมาจากประกาศตัวแปร และ nullness ของการเรียกเมธอดมาจากชนิดคืนค่าของเมธอดที่อ้างถึง
  • ลิเทอรัล null เป็น nullable
  • นิพจน์ชนิดอ้างอิงอื่น ๆ ส่วนใหญ่เป็น null-restricted
    • รวมถึงลิเทอรัล การต่อสตริง this การสร้างอินสแตนซ์คลาส การสร้างอาร์เรย์ การอ้างอิงเมธอด และนิพจน์แลมบ์ดา
  • อนุญาตการแปลง nullness ในบริบทการกำหนดค่า การเรียก และการแคสต์
  • การแปลง nullness แบบขยาย รวมถึง
    • Foo!Foo?
    • Foo!Foo แบบไม่ได้ระบุ
    • Foo?Foo แบบไม่ได้ระบุ
    • Foo แบบไม่ได้ระบุ → Foo?
  • การแปลง nullness แบบแคบลง รวมถึง
    • Foo?Foo!
    • Foo แบบไม่ได้ระบุ → Foo!
  • การแปลง nullness แบบแคบลงจะถูกคอมไพเลอร์ทำโดยอัตโนมัติคล้ายการแปลง unboxing แต่ตอนรันไทม์จะทำการตรวจสอบแบบไดนามิกซึ่งอาจทำให้เกิด NullPointerException
  • ความพยายามแปลงลิเทอรัล null ไปเป็นชนิด null-restricted โดยตรงเป็นข้อผิดพลาดตอนคอมไพล์

การตรวจสอบตอนรันไทม์และข้อยกเว้น

  • หากค่า null ถูกแปลง nullness แบบแคบลงเป็นชนิด null-restricted ตอนรันไทม์ จะเกิด NullPointerException
  • การแปลง nullness แบบแคบลงที่ไม่ปรากฏอย่างชัดเจนในซอร์สโค้ดก็อาจเกิดขึ้นระหว่างการรันได้
  • อาร์เรย์ที่ถูกกำหนดให้มีชนิดคอมโพเนนต์แบบ null-restricted จะปฏิเสธค่า null ในการตรวจสอบการเก็บลงอาร์เรย์ตามปกติ แม้จะถูก扱ายในซอร์สโค้ดด้วยชนิดที่เจาะจงน้อยกว่า
    • ความล้มเหลวในการแปลงนี้ทำให้เกิด ArrayStoreException
  • หากฟิลด์ที่ไม่ใช่ null-restricted ตอนคอมไพล์ภายหลังกลายเป็น null-restricted จากการคอมไพล์แยกกัน field store check แบบใหม่จะปฏิเสธการเก็บ null
    • ความล้มเหลวในการแปลงนี้ทำให้เกิด FieldStoreException
  • ในการเรียกเมธอดที่มีความสัมพันธ์แบบ overriding อาจมีการแปลงไปเป็นชนิดการเรียกของพารามิเตอร์เมธอดระดับบนก่อน แล้วจึงตามด้วยการแปลงไปเป็นชนิดพารามิเตอร์ของเมธอดที่ override
  • ค่าคืนกลับของเมธอดก็อาจถูกแปลงเป็นชนิดคืนค่าของเมธอดที่ประกาศไว้ก่อน แล้วจึงแปลงเป็นชนิดคืนค่าที่คาดหวัง ณ จุดเรียก

เจเนอริก อาร์กิวเมนต์ชนิด และ overriding

  • การใช้ type variable ก็สามารถใส่มาร์กเกอร์ nullness ได้ โดย T! เป็นชนิด null-restricted และ T? เป็นชนิด nullable
  • ชนิด type variable แบบ null-restricted และ nullable เป็นการยืนยัน nullness เฉพาะภายในโค้ดเจเนอริก
  • ชนิดที่ใช้เป็นอาร์กิวเมนต์ชนิดก็สามารถแสดง nullness ได้ และมาร์กเกอร์ nullness ที่ติดกับชนิด type variable จะทับ nullness ที่ยืนยันไว้ในอาร์กิวเมนต์ชนิด
  • ภายในอิมพลีเมนต์แบบ erased ของ API เจเนอริก ไม่สามารถบังคับใช้การจำกัด null ได้
    • อย่างไรก็ตาม implicit cast ตามปกติที่เกิดขึ้นตรงขอบเขต API เจเนอริกจะบังคับใช้อาร์กิวเมนต์ชนิดแบบ null-restricted ตอนรันไทม์
  • เพื่อการทำงานร่วมกัน nullness ภายในอาร์กิวเมนต์ชนิดจะไม่ถูกบังคับใช้อย่างเข้มงวด
    • Predicate<String!> สามารถแปลงเป็น Predicate<String> หรือ Predicate<String?> ได้
    • unchecked nullness conversion แบบนี้อาจก่อให้เกิดคำเตือน
  • การเปลี่ยน nullness ของชนิดคอมโพเนนต์อาร์เรย์ก็อนุญาตในฐานะ unchecked nullness conversion และเงื่อนไขที่จะมีการตรวจสอบตอนรันไทม์ยัง TBD
  • ในการพิจารณาความเหมือนของเมธอดซิกเนเจอร์ จะเพิกเฉยต่อ nullness
    • เมธอดหนึ่งสามารถ override อีกเมธอดได้แม้ nullness ของพารามิเตอร์และชนิดคืนค่าไม่ตรงกัน
    • หาก API ต่าง ๆ นำมาร์กเกอร์ nullness มาใช้แยกกัน ความไม่ตรงกันลักษณะนี้อาจพบได้บ่อย
  • nullness ไม่มีผลต่อ applicability ของเมธอด และไม่สามารถทำให้การอนุมานอาร์กิวเมนต์ชนิดล้มเหลวได้ แต่อาจมีผลต่อ nullness ที่อนุมานสำหรับชนิดคืนค่าของเมธอดเจเนอริก
    • รายละเอียดของอัลกอริทึมอนุมานยัง TBD

คำเตือนและข้อผิดพลาดจากคอมไพเลอร์

  • การทำให้ชนิดเป็น null-restricted อาจทำให้เกิดข้อผิดพลาดใหม่ตอนคอมไพล์
    • เมื่อฟิลด์หรืออาร์เรย์ของชนิดนั้นไม่ได้ถูกเริ่มต้น
    • เมื่อพยายามแปลงลิเทอรัล null เป็นชนิดนั้น
    • การเปรียบเทียบลิเทอรัล null กับนิพจน์ชนิด null-restricted ก็อาจกลายเป็นข้อผิดพลาดตอนคอมไพล์ได้
  • ในสถานการณ์อื่น ๆ การวิเคราะห์ nullness เป็นเพียงตัวช่วยและไม่สร้างข้อผิดพลาดตอนคอมไพล์
  • javac จะให้คำเตือนเพื่อหลีกเลี่ยงข้อผิดพลาดตอนรันไทม์ และแนะนำให้ IDE กับเครื่องมือวิเคราะห์อื่น ๆ เดินไปในทิศทางเดียวกัน
  • สาเหตุที่เป็นไปได้ของคำเตือนมีดังนี้
    • การแปลง nullness แบบแคบลง รวมถึงกรณีที่มาจากชนิด ?
    • การใช้นิพจน์ชนิด ? กับการเข้าถึงสมาชิกหรือการดำเนินการอื่นที่ไม่เป็นมิตรต่อ null
    • เมื่อ nullness ของอาร์กิวเมนต์ชนิดไม่สอดคล้องกับ bound
    • เมื่อ nullness ของพารามิเตอร์เมธอดหรือค่าคืนกลับไม่ตรงกับเมธอดที่ถูก override
    • unchecked conversion ที่เปลี่ยน nullness ของชนิด

ไฟล์คลาส รีเฟลกชัน และการเปลี่ยนแปลงประกอบ

  • การใช้มาร์กเกอร์ null ส่วนใหญ่จะถูกลบทิ้งในไฟล์ class และการแปลงรันไทม์ที่เกี่ยวข้องจะแสดงโดยตรงใน bytecode
  • ไวยากรณ์ของแอตทริบิวต์ Signature จะถูกอัปเดตให้อนุญาต ! และ ? ภายในชนิด
  • nullness จะไม่ถูกเข้ารหัสใน descriptor ของเมธอดและฟิลด์
  • เพื่อป้องกันการปนเปื้อนของฟิลด์ จะมีแอตทริบิวต์ใหม่ NullRestricted เพื่อระบุว่าฟิลด์ไม่อนุญาตค่า null
    • ฟิลด์นี้ต้องถูกทำเครื่องหมาย ACC_STRICT ด้วย และต้องถูกเริ่มต้นอย่างเข้มงวด
    • verifier จะตรวจสอบว่าฟิลด์อินสแตนซ์แบบ strict initialization ทั้งหมดถูกกำหนดค่าแล้วหรือไม่ ณ เวลาที่คอนสตรักเตอร์เรียก super(...)
    • ความพยายามเขียนลงฟิลด์ทั้งหมดจะตรวจสอบค่า null และหากพบจะโยน FieldStoreException
  • การสร้างอาร์เรย์แบบ null-restricted ไม่รองรับด้วยคำสั่ง anewarray และต้องทำผ่านการเรียก reflection API
  • ไม่มีลิเทอรัล Foo!.class หรือ Foo?.class และไม่มีอินสแตนซ์ java.lang.Class ที่สอดคล้องกัน
  • RuntimeType API ใหม่จะอธิบายชุดชนิดที่ถูกบังคับใช้ตอนรันไทม์ในการตรวจสอบการเก็บลงอาร์เรย์และฟิลด์ รวมถึงรูปแบบ null-restricted ของชนิดคลาสและอินเทอร์เฟซทั้งหมด
  • Field API รองรับการดู RuntimeType ของฟิลด์ และค่านี้อาจแตกต่างจากผลของ getType
  • Array API รองรับรูปแบบ newInstance ที่แสดงชนิดคอมโพเนนต์ด้วย RuntimeType
    • รูปแบบนี้อนุญาตให้ระบุค่าเริ่มต้นของคอมโพเนนต์อาร์เรย์ด้วย
    • ปฏิเสธความพยายามสร้างอาร์เรย์แบบ null-restricted ที่ไม่มีค่าเริ่มต้น
  • deserialization แบบดั้งเดิมไม่เข้ากันกับฟิลด์และอาร์เรย์แบบ null-restricted และ JEP แยกต่างหากจะจัดเตรียมกลไก serialization ที่ไม่เปิดเผยฟิลด์และอาร์เรย์แบบ null-restricted ที่ยังไม่ได้เริ่มต้น
  • เอกสารที่ javadoc สร้างจะรวมมาร์กเกอร์ nullness ด้วย
  • API java.lang.reflect.Type และ javax.lang.model จะเข้ารหัส nullness ในการแทนชนิด

ทางเลือกและความสัมพันธ์พึ่งพา

  • เครื่องมือนักพัฒนาหลายตัวในระบบนิเวศ Java เคยติดตาม null ของตนเองตอนคอมไพล์ แต่เพราะไม่ได้เปลี่ยนภาษา Java ไวยากรณ์จึงมักจำกัดอยู่ที่ annotation และพฤติกรรมที่ส่งผลได้ก็จำกัดอยู่ที่การตรวจสอบตอนคอมไพล์
  • ภาษาอื่นติดตาม nullness ในระบบชนิด และหลายภาษามีค่าเริ่มต้นเป็น null-restricted พร้อมมองว่าการกำหนดค่าให้ชนิด null-restricted โดยไม่มีการตรวจสอบ null อย่างชัดเจนเป็นข้อผิดพลาด
  • ใน Java ฟีเจอร์ต้องเป็นแบบเลือกใช้ได้ และต้องใช้ได้อย่างค่อยเป็นค่อยไปโดยไม่ต้องทำ migration ขนาดใหญ่ครั้งเดียว
  • การบังคับใช้ nullness ตอนรันไทม์สามารถทำได้ด้วยการตรวจสอบแบบชัดเจนหรือการเรียก Objects.requireNonNull
    • แต่การใช้อย่างสม่ำเสมอนั้นยุ่งยาก ต้องมีเอกสารเพิ่มเติม และทำให้อ่านโปรแกรมยากขึ้น
    • ไม่มีวิธีใช้โดยตรงกับที่เก็บตัวแปรอย่างฟิลด์และอาร์เรย์
  • เงื่อนไขก่อนหน้าคือ Flexible Constructor Bodies (Second Preview)
    • คอนสตรักเตอร์สามารถรัน statement และกำหนดค่าให้ฟิลด์อินสแตนซ์ก่อนเรียก super(...) ได้ จึงทำให้ข้อกำหนดการเริ่มต้นฟิลด์แบบ null-restricted เป็นไปได้
  • งานในอนาคตรวมถึงสิ่งต่อไปนี้
    • Null-Restricted Value Class Types (Preview): การปรับแต่งการเข้ารหัสของฟิลด์และอาร์เรย์ที่มีชนิดเป็น value class แบบ null-restricted
    • JEP 402: Enhanced Primitive Boxing (Preview): การติดตาม nullness เมื่อภาษาใช้การแปลง boxing โดยนัยได้กว้างขึ้น
    • การทำ specialization ของคลาสและเมธอดใน JVM (JEP 218 และฉบับปรับปรุง): ทำให้ nullness ของอาร์กิวเมนต์ชนิดบางส่วนเป็นรูปธรรมและบังคับใช้
  • การปรับปรุงเพิ่มเติมที่อาจมีในอนาคต ได้แก่ การใส่มาร์กเกอร์ nullness ให้ API มาตรฐานบางส่วน การแสดง null check ใน bytecode ให้กระชับ การบังคับใช้ระดับต่ำที่เข้มงวดยิ่งขึ้นสำหรับพารามิเตอร์เมธอดแบบ null-restricted และกลไกภาษาที่ประกาศโดยนัยให้ชนิดทั้งหมดในบางบริบทเป็น null-restricted

1 ความคิดเห็น

 
GN⁺ 2024-08-04
ความคิดเห็นจาก Hacker News
  • น่าสนใจที่แนวทางนี้ต่างจากวิธีที่ C# นำมาใช้เมื่อไม่กี่ปีก่อน ใน C# ถ้าเปิดใช้งาน nullability ในโปรเจกต์ ตัวแปรทั้งหมดจะถูกประกาศเป็น non-null เว้นแต่จะระบุอย่างชัดเจนว่าเป็น nullable ส่วนข้อเสนอนี้ทำให้ตัวแปรเดิม ๆ กลายเป็นหนึ่งในสามแบบโดยพฤตินัย: nullable, nullable แบบชัดเจน, หรือ non-nullable แบบชัดเจน
    Kotlin ก็เป็นภาษา JVM เช่นกัน แต่เหมือน C# ตรงที่ถ้าไม่ระบุจะถือว่าเป็น non-null เพียงแต่ Kotlin ไม่มีภาระเรื่องความเข้ากันได้ย้อนหลัง นอกจากนี้ยังมีทางเลี่ยงอย่าง lateinit var ที่ทำให้ปล่อยค่าในประเภท non-nullable ว่างไว้ได้จนกว่าจะถูกกำหนดค่าในเมธอดอื่น และถ้าเข้าถึงก่อนกำหนดค่าก็จะโยนข้อยกเว้นเฉพาะให้
    สงสัยว่าทำไมถึงมี ตัวเลือกสามแบบ กันแน่ รู้สึกว่าน่าจะคงตัวแปรที่ไม่มี annotation ให้เป็น nullable แล้วให้เฉพาะตัวแปรที่มี annotation เป็น non-null แบบชัดเจนก็ได้ ถ้าไม่ใส่อะไรเลยก็เป็น nullable โดยอัตโนมัติอยู่แล้ว ก็คิดไม่ออกว่าทำไมถึงอยากประกาศ nullable อีก
    ผมชอบแนวทางของ C# มากกว่า แต่แนวทางนี้มีข้อดีตรงที่ใช้กับโค้ดเบส legacy ได้โดยไม่ต้องแก้ปัญหา nullability ทั้งหมดให้เสร็จ ในทางกลับกัน C# จะเผยปัญหา nullability ออกมาทันที แต่ข้อเสนอนี้ยังซ่อนมันไว้ได้เหมือนเดิม
    อีกอย่าง ส่วนที่ว่า “เมธอดหนึ่งสามารถ override เมธอดอื่นได้แม้ nullness ของพารามิเตอร์และค่าที่คืนกลับจะไม่ตรงกัน” ก็ดูแปลก น่าจะกลายเป็น ปืนยิงเท้าตัวเอง เวลา override/implement callback ที่เมธอดเดิมระบุว่าคืนค่า non-null แต่ตัวที่เขียนกลับคืนค่า null

    • ตอนนี้ที่บริษัทกำลังย้ายโค้ดเบส C# legacy ไปใช้ nullable reference types อยู่ ถ้ามี เครื่องหมาย non-nullable แบบชัดเจน ก็คงดีมาก เพราะจะรู้ได้ทันทีว่าส่วนไหนตรวจทานและใส่ annotation แล้ว
      ด้วยข้อจำกัดด้านเวลา ตอนนี้เราเลือกเปิด annotation เป็นหลัก ยกเว้นบางจุดสำคัญไม่กี่แห่ง แต่ยังคงใช้ NotNullAttribute และ CanBeNullAttribute ของ JetBrains เป็นเครื่องหมายอยู่ เพื่อให้เห็นทันทีว่าส่วนไหนตัดสินใจอย่างตั้งใจแล้ว ตัวหลังสามารถลบได้เพราะ nullable มีเครื่องหมายชัดเจนอยู่แล้ว แต่ตัวแรกมีปัญหาชื่อชนกับฟีเจอร์ของ C# เอง
      ในแง่นั้น ตัวเลือกสามแบบจึงค่อนข้างน่าพึงประสงค์ เพราะถ้าโค้ดมีเป็นแสน ๆ บรรทัด การย้ายให้เร็วและง่ายไม่ใช่เรื่องง่าย
      ในโปรเจกต์ภายในอื่น ๆ เรากำลังค่อย ๆ ขยายขอบเขต nullability โดยโปรย #nullable enable รอบ ๆ โค้ดที่แตะอยู่ และยังกำหนดด้วยว่าโค้ดใหม่ต้องอยู่ใน nullable context วิธีนี้ก็พอใช้ได้ในการระบุส่วนที่ใส่ annotation แล้ว แต่เป็นแนวทางที่ทำได้กับโค้ดเบสและทีมที่เล็กกว่ามาก
    • ใน Kotlin ก็มีประเภทที่ระบุ nullability ไม่ได้โดยตรงเช่นกัน Kotlin เรียกสิ่งนี้ว่า platform types และในข้อความผิดพลาดกับ diagnostic จะแสดงด้วยเครื่องหมายอัศเจรีย์ เช่น String!
      คำว่า “platform type” กับสัญลักษณ์อัศเจรีย์อาจทำให้สับสน แต่โดยรวมแล้วแนวทางของ Kotlin ทำงานได้ค่อนข้างดี เพราะ Kotlin มี nullability ตั้งแต่แรก โปรแกรมเมอร์จึงกำหนด platform type โดยตรงไม่ได้ แต่ก็ยังจำเป็นเพื่อให้เข้ากันได้กับแพลตฟอร์มพื้นฐานที่ nullability ไม่ชัดเจน เช่น JVM หรือ JavaScript
      ในแนวทางนี้ ค่าเริ่มต้นยังสมเหตุสมผลอยู่ โดยพื้นฐานแล้วควรเป็น non-nullable เสมอ และใครที่คิดต่างก็เท่ากับไม่ได้เรียนรู้อะไรจาก Tony Hoare เลย ในขณะเดียวกันก็ยังรักษาความเข้ากันได้ย้อนหลังไว้ได้ Kotlin ถือว่าง่ายกว่า ส่วน Java กับ C# ต้องรักษาความเข้ากันได้กับซอร์สโค้ดเดิมด้วย
      ทั้งแนวทางของ Java และของ C# ต่างก็ไม่ใช่อุดมคติ แนวทางของ C# เปลี่ยนพฤติกรรมของโค้ดไปมากตาม compiler flag ส่วนแนวทางของ Java ทำให้ค่าเริ่มต้นเป็นตัวเลือกที่แย่ที่สุด
      ถึงอย่างนั้นก็เอนเอียงไปทาง C# มากกว่า เพราะถ้าทำให้ “อาจเป็น null ได้” เป็นตัวเลือกที่ง่ายที่สุด โปรแกรมเมอร์ส่วนใหญ่ก็จะเลือกสิ่งนั้นเป็นค่าเริ่มต้น โดยเฉพาะในภาษาแนวองค์กรอย่าง Java น่าจะยิ่งเป็นแบบนั้น Linter และ warning ของคอมไพเลอร์คงช่วยได้ในระยะยาว แต่กว่าจะถึงวันที่โค้ด Java ส่วนใหญ่ใส่ annotation เรื่อง nullability อย่างถูกต้องน่าจะใช้เวลาหลายปี ผู้ใช้ C# อาจเจ็บปวดกว่าในระยะสั้น แต่มีโอกาสไปถึงเป้าหมายเรื่อง nullability ที่ชัดเจนได้เร็วกว่ามาก
      https://kotlinlang.org/docs/java-interop.html#null-safety-an...
      https://www.infoq.com/presentations/Null-References-The-Bill...
    • เห็นด้วยว่ามันแปลก วิธีที่คาดไว้คือในระดับซอร์สโค้ด T? หมายถึง nullable, T! หมายถึง non-nullable และค่าเริ่มต้นของ T ธรรมดาในซอร์สที่คอมไพล์ใหม่ควรถูกกำหนดได้ด้วยประกาศคล้าย pragma แบบรายไฟล์ซอร์ส, รายไฟล์ package-info, หรือแบบ global ผ่าน compiler switch
      จากนั้นใน Java รุ่น LTS ถัด ๆ ไปค่อยเปลี่ยนค่าเริ่มต้นแบบ global ก็ได้ วิธีนี้จะทำให้การย้ายโปรเจกต์ง่ายขึ้น สามารถแทรก/อัปเดต pragma อัตโนมัติได้ และถ้าจำเป็นก็ยังคงค่าเริ่มต้นแบบเดิมไว้ได้
      นอกจากนี้ ถ้านำ annotation @Nonnull ของ JSR-305 ที่มีอยู่แล้วมาใช้เป็นวิธีระบุการปรากฏของประเภท non-nullable ในไฟล์คลาส ก็น่าจะให้ความเข้ากันได้แบบสองทางกับ JDK รุ่นเก่าได้ด้วย
    • ตอนท้ายมีส่วนที่ว่า “ให้กลไกในภาษาเพื่อยืนยันว่าทุกประเภทในบริบทหนึ่ง ๆ ถูกจำกัดไม่ให้เป็น null โดยปริยาย แม้โปรแกรมเมอร์จะไม่เขียนสัญลักษณ์ ! อย่างชัดเจน”
      แค่มี compiler flag หรือ tag ระดับโมดูลก็น่าจะโอเคแล้ว
    • เหตุผลน่าจะเป็นเพราะ ความเข้ากันได้ย้อนหลังของโค้ด ซึ่ง Java ให้ความสำคัญมากกว่าภาษาอื่นมาก
      ถึงอย่างนั้นก็คาดว่าน่าจะมี compiler flag ที่ทำให้ถ้าไม่ระบุเป็นอย่างอื่น จะถือว่าเป็น non-nullable โดยอัตโนมัติ
  • ดูดีนะ ในที่สุดก็มีวิธีในระดับภาษาเพื่อกำจัด exception ที่ไม่จำเป็นเป็นพัน ๆ ตัวและการตรวจสอบ null ได้แล้ว แต่การแปลงอัตโนมัติที่ทำให้ขอบเขตของความเป็น null แคบลงยังรู้สึกไม่ค่อยถูกต้อง
    ในตัวอย่างของข้อเสนอ String? id(String! arg) { return arg; }, String s = null;, Object! o1 = s; // NPE, Object o2 = id(s); // NPE, Object o3 = (String!) s; // NPE อย่างน้อยสองกรณีแรกควรเป็น ข้อผิดพลาดตอนคอมไพล์ ไม่ใช่หรือ
    กรณีสุดท้ายเป็นการระบุชัดเจนเลยค่อนข้างก้ำกึ่ง แต่ผมว่าให้คอมไพเลอร์รู้ชนิดที่มีผลเป็น String! ภายใน if (s != null) แล้วอนุญาต String! ss = s; แบบนั้นจะดีกว่า แบบนั้นจะไม่มีความเป็นไปได้ที่จะเกิดข้อผิดพลาด

    • ในฐานะคนที่เคยใช้ Java ในอุตสาหกรรม ผมคิดว่าการตรวจสอบแบบไดนามิกควรเกิดขึ้นเฉพาะเมื่อร้องขออย่างชัดเจนเท่านั้น ส่วนที่เหลือควรจัดการแบบสแตติกทั้งหมด Bean Validation ทำงานได้ดีในแง่ที่ว่าแม้อ็อบเจกต์อาจอยู่ในสถานะผิดชั่วคราวได้ แต่จะถือว่าถูกต้อง ณ ตอนที่ตรวจสอบอย่างชัดเจน หรือ ณ ตอนที่เฟรมเวิร์กตรวจสอบก่อนเข้ามาในโค้ดของเรา
      จริง ๆ แล้วเมื่อเทียบกับการ cast ในกรณีสุดท้าย ผมชอบใช้ Objects.requireNonNull(s) มากกว่า เพราะชัดเจนกว่า แต่ถ้ามีอะไรอย่าง Objects.unsafeForceNonNull(s) ที่ข้ามการตรวจสอบแบบชัดเจนได้ ยกเว้นเฉพาะกรณีที่ถูกกันไว้เพราะ optimization ก็คงดี ถ้ามีเมธอด unsafe ก็จะสามารถ implement requireNonNull เองได้โดยไม่ต้องเพิ่มการวิเคราะห์สแตติกที่ซับซ้อน
    • มีเขียนไว้ว่า “ไม่ใช่เป้าหมายที่จะบังคับให้โปรแกรมจัดการค่า null ทุกค่าที่อาจเกิดขึ้นอย่างชัดเจน และค่า null ที่ไม่ได้จัดการอาจเป็นคำเตือนตอนคอมไพล์ได้ แต่ไม่ใช่ข้อผิดพลาด”
      น่าเสียดายที่เรื่องนี้จะตรวจพบได้เฉพาะที่ รันไทม์ เท่านั้น
    • ถ้าตัวอย่างแรกหรือที่สองกลายเป็นข้อผิดพลาดตอนคอมไพล์ ก็หมายความว่าต้องใส่ annotation ทุกจุดที่ใช้ไลบรารี จนกว่าไลบรารีนั้นจะย้ายไปใช้ชนิด null หรือถ้าไม่มีวันย้ายเลย แทบทุกบรรทัดก็คงเต็มไปด้วย cast ซึ่งไม่สมเหตุสมผล
      เช่น standard library ระบุไว้อย่างชัดเจนว่าอย่างน้อยในตอนนี้ จะไม่ย้ายไปใช้ชนิด null
    • ถ้าแก่นของปัญหาคือการนำ null เข้ามาใน API ผมก็ไม่แน่ใจว่านั่นเป็นข้อผิดพลาดของภาษาหรือไม่ เข้าใจว่ามันยืดยาวและต้องมีอะไรช่วยจัดระเบียบ แต่สุดท้ายปัญหาอยู่ที่นักพัฒนาที่เขียนโค้ดแบบนั้นไม่ใช่หรือ
    • ถ้าข้อที่สองไม่ใช่ข้อผิดพลาดตอนคอมไพล์ โค้ด legacy ก็จะเรียกฟังก์ชันที่รับอาร์กิวเมนต์ non-null ได้ แต่ไม่แน่ใจว่านั่นดีจริงไหม คงตัดสินยากก่อนจะได้ลองใช้จริง
  • ดูเหมือนจำเป็นมากที่จะต้องมีวิธีทำเครื่องหมายตัวแปรทั้งหมดเป็นค่าเริ่มต้นแบบ non-null ในระดับแพ็กเกจ หรืออย่างน้อยระดับไฟล์ ไม่อย่างนั้นข้อเรียกร้องให้ใช้ไวยากรณ์ T! กับตัวแปรแทบทุกตัวเพื่อความปลอดภัยคงจะแรงขึ้น และนั่นจะสร้าง noise จำนวนมากเท่านั้น

    • ใต้หัวข้อ “การปรับปรุงที่เป็นไปได้ในอนาคต” มีข้อว่า “มีกลไกในภาษาที่ให้โปรแกรมเมอร์ประกาศได้ว่าทุกชนิดในบริบทหนึ่ง ๆ ถูกจำกัดไม่ให้เป็น null โดยนัย โดยไม่ต้องใช้สัญลักษณ์ ! อย่างชัดเจน”
    • ผมว่าแบบนั้นก็ไม่ได้แย่มาก โปรเจกต์ของเรามีมาตรฐานอยู่แล้วว่าตัวแปรทุกตัวในโค้ด Java ที่เราเป็นเจ้าของจะไม่เป็น null ถ้าต้องการตัวแปร nullable ก็ต้องใส่ annotation @Null
      ปัญหาจะเกิดเฉพาะตรงขอบเขตของโค้ดที่โต้ตอบกับไลบรารี ไวยากรณ์ใหม่นี้ก็น่าจะคล้ายกัน
    • linter ที่เข้มงวดอาจถือว่าชนิดที่ไม่ได้ทำเครื่องหมายเป็น non-null และบังคับให้ระบุความเป็น null ได้เฉพาะในจุดที่จำเป็นก็ได้
  • ส่วนที่บอกว่า “ในตอนนี้ เป้าหมายไม่ใช่การนำการปรับปรุงของภาษาไปใช้กับ standard library” น่าเสียดาย
    จากประสบการณ์ที่จำเป็นต้องใช้ PHP เวลาต้องถอดคุณสมบัติที่รับประกันไว้ล่วงหน้าของข้อมูลออก หรือประกอบกลับเข้าไปใหม่ทุกครั้งที่โต้ตอบกับ standard library ขนาดใหญ่นั้นยุ่งยาก
    Java ก็ควรใส่พลังในการแสดงออกแบบนี้เข้าไปใน standard library ให้มากขึ้นและทำให้มันเป็น first-class citizen

    • หนึ่งในปัญหาใหญ่ที่สุดของ Java คือเพราะค่อย ๆ ปรับปรุงทีละน้อย โค้ดที่เพิ่งดีขึ้นเล็กน้อยก็กลายเป็นโค้ด legacy อีกครั้งในฟีเจอร์ถัดไปที่เพิ่มเข้ามาแบบค่อยเป็นค่อยไป โค้ด legacy ที่ใช้ Optional จะกลายเป็นภาระต่อข้อเสนอ nullable/non-nullable แบบชัดเจน ผมเคยคิดว่า record type ก็น่าจะเป็น non-nullable โดยค่าเริ่มต้นได้
    • ดูเหมือนคุณกำลังอ่าน “ในตอนนี้” เป็น “จะไม่มีวันทำ” แต่จริง ๆ แล้วการเปลี่ยนผ่านนั้นเป็นงานขนาดใหญ่มาก จึงใกล้เคียงกับการจะแยกทำต่างหากมากกว่า
      อีกอย่าง การแบ่งเป็นสองขั้นจะทำให้ปล่อยฟีเจอร์นี้เป็น preview feature ได้ง่ายขึ้น รับฟีดแบ็ก แล้วค่อยล็อกการออกแบบให้แน่นอน ถ้าพยายามทำทั้งหมดในครั้งเดียว ก็แทบจะไม่มีพื้นที่ให้ iterate ปรับปรุงฟีเจอร์จากการใช้งานจริง
    • คำสำคัญคือ “ในตอนนี้” ผมคิดว่าเมื่อฟีเจอร์เข้าที่แล้ว มันจะกลายเป็นเป้าหมาย
    • ใช้คำว่าจำเป็นอย่างเสียไม่ได้เลยเหรอ PHP สมัยใหม่ค่อนข้างใช้งานสบายทีเดียว
  • ถ้าฟีเจอร์นี้เข้ามาใน Java ได้ก็คงดี ความสามารถในการเลือกอย่างชัดเจนในระดับภาษาอย่าง T? ช่วยยกระดับคุณภาพชีวิตของนักพัฒนาใน Kotlin และ TypeScript อย่างมาก Java มีเครื่องมืออย่าง NullAway อยู่ แต่ยุ่งยาก
    ผมมองว่าการรองรับในระดับภาษาดีกว่า Optional/Maybe มาก เพราะทำให้โฟกัสกับ logic จริง แทนที่จะต้องเอาโค้ดไปวางบนราง map/flatMap
    https://github.com/uber/NullAway

    • จะให้ทำลาย source compatibility ที่มีมากว่า 30 ปีเลยหรือ ทำไมต้องทำอย่างนั้นด้วย
      แค่ใช้ภาษา JVM อื่นก็ได้นี่นา
  • ตอนนี้ไซต์ล่มอยู่ เลยวางลิงก์ archive ไว้: https://web.archive.org/web/20240802081039/https://bugs.open...

    • ไม่น่าแปลกใจเลยที่เปลี่ยนไปใช้ “jira” ที่ทุกคนรัก
  • “การบังคับให้โปรแกรมต้องจัดการค่า null ทั้งหมดที่อาจเกิดขึ้นอย่างชัดเจนไม่ใช่เป้าหมาย และค่า null ที่ไม่ได้จัดการอาจเป็นคำเตือนตอนคอมไพล์ได้ แต่ไม่ใช่ข้อผิดพลาด” เป็นการตัดสินใจที่แย่
    Java ส่วนใหญ่เป็น ภาษาแบบ static typing แล้วทำไมถึงใส่พฤติกรรมแบบ dynamic เพิ่มเข้าไปอีกก็ไม่รู้ หวังว่าจะมีวิธีง่าย ๆ ในการยกระดับคำเตือนแบบนี้ให้เป็นข้อผิดพลาด

    • ทำแบบนั้นไม่ได้ เพราะจะทำให้โปรแกรม Java ที่มีอยู่ทั้งหมดพัง ถ้าบังคับเช่นนั้น ทุกโปรแกรมจะต้องถูกเขียนใหม่ให้จัดการผลตอบกลับที่เป็น null ในทุกที่ ฟีเจอร์ที่เพิ่มมาครั้งนี้บังคับการจัดการ null ได้เฉพาะกับการดำเนินการที่เกี่ยวข้องกับชนิดข้อมูลใหม่เท่านั้น จึงไม่ทำให้อะไรพัง
      ภาษาที่ type system บังคับให้จัดการ null/nil ในทุกกรณีนั้นดีกว่ามากจริง ๆ แต่ Java ในปัจจุบันไม่ใช่ภาษาแบบนั้น ถึงอย่างนั้น นี่ก็น่าจะเป็นการปรับปรุงครั้งใหญ่
  • น่าเสียดายที่บทเรียนแบบนี้เพิ่งมาเรียนรู้กันช้าเกินไป โดยพื้นฐานควรเป็น non-nullable, เป็น immutable โดยปริยาย และมี scope ที่แคบที่สุดโดยปริยาย
    ในการออกแบบใหม่ ๆ มักเลือกความสะดวกเฉพาะหน้าแทนที่จะ “ทำให้ไหลไปสู่เส้นทางที่ปลอดภัย” บ่อยเกินไป ค่าเริ่มต้นที่ปลอดภัยต้องการการออกแบบและประสบการณ์ผู้ใช้ที่พิถีพิถันกว่ามาก แต่ผลลัพธ์คือแทบทุกภาษา·แพลตฟอร์ม·เทคโนโลยีเต็มไปด้วยปืนที่เล็งใส่เท้าตัวเอง วิศวกรรมโยธาและไฟฟ้ามีกฎระเบียบ แต่ซอฟต์แวร์กลับเรียนบทเรียนเดิมซ้ำในภาษาและเทคโนโลยีใหม่ ๆ ทุกประมาณ 30 ปี

    • น่าเสียดายที่ Java ดูเหมือนยังไม่ได้เรียนรู้กฎ non-nullable โดยปริยาย ข้อเสนอนี้นำชนิดข้อมูลใหม่สองแบบเข้ามา แต่ชนิดข้อมูลเดิมที่ไม่มี annotation ถ้าถามว่า nullable หรือไม่ คำตอบก็ยังเป็น “ก็ไม่แน่”
    • สิ่งที่น่าเสียดายคือ Java เรียนรู้เรื่องพวกนี้ช้า “เรา” กับ “Java” ไม่ใช่สิ่งเดียวกัน
      Java เหมือนหมู่บ้านในโลกที่สามที่เพิ่งเรียนรู้ว่าถ้าจะฆ่าเชื้อโรคก็ให้ต้มน้ำ
  • งานส่วนใหญ่ที่ทำที่ Facebook คือการใช้ Hack ความเป็น null ได้หรือไม่เป็นองค์ประกอบหลักของ type system ใน Hack และช่วยแก้ข้อผิดพลาดไร้สาระได้มากมายจริง ๆ
    แน่นอนว่าไม่ได้หมายความว่าจะไม่มีทางได้รับ null ที่ไม่คาดคิดเลย เพราะฟีเจอร์นี้ก็ถูกเพิ่มเข้ามาในภาษาภายหลัง จึงยังมีชนิด mixed แบบ legacy ที่สะท้อนรากจาก PHP อยู่มาก ซึ่งโดยพื้นฐานแล้วหมายความว่าเป็นอะไรก็ได้
    ก่อนอื่นสงสัยว่า nullable array จะเป็นอย่างไร ตัวอย่าง String![] แสดงกรณีที่ object อาจเป็น null ได้ แต่ตัว array เองล่ะ? ใน Java นั้น String labels[] = null; ถูกต้องตามกฎหมายเต็มที่ งั้นต้องประกาศแบบ String![]! labels; หรือเปล่า?
    ใน Hack นั้น vec $foo หมายถึงทั้ง foo และ element ไม่ใช่ null ส่วน ?vec $foo หมายถึง element เป็น non-null แต่ foo เป็น null ได้ ในทางปฏิบัติแทบไม่มีเหตุผลให้ใช้ array ที่เป็น null ดังนั้นค่าเริ่มต้นควรเป็นห้าม null อย่างไรก็ตาม Java มีปัญหาว่าโค้ด legacy ทั้งหมดตั้งสมมติฐานว่ามีความเป็นไปได้ที่จะเป็น null
    จากตัวอย่าง Object! o1 = s, Object o2 = id(s), Object o3 = (String!) s ในข้อเสนอ รู้สึกว่าข้อ 2 และ 3 ควรเป็นข้อผิดพลาดตอนคอมไพล์ไม่ใช่หรือ
    สุดท้ายนี้ ผมชอบตัวดำเนินการบังคับ as ของ Hack มากกว่าการ cast ของ Java เช่น foo($b) เป็นข้อผิดพลาดตอนคอมไพล์, $b as A ถ้าเป็น null จะเกิดข้อผิดพลาดตอน runtime, $a as ?B ถ้าเป็น B ก็ cast ถ้าไม่ใช่ก็คืนค่า null เป็นต้น
    สุดท้ายคำถามคือจะสามารถครอบสิ่งนี้ทับ Java SDK ได้หรือไม่ และในโค้ด legacy จะมีหน้าตาเป็นอย่างไร
    https://docs.hhvm.com/hack/types/nullable-types

    • น่าจะเป็นแค่ String![]? labels = null; มั้ง
  • ของดี ๆ จาก Kotlin ดูเหมือนกำลังทยอยเข้ามาใน Java หมดแล้ว
    ถึงอย่างนั้นก็ยังอยากทำงานต่อใน Kotlin ที่ไม่ต้องรับมือกับอะไรอย่าง Lombok อยู่ดี Java record ก็ดีอยู่หรอก

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