ย้ายจาก instanceof มาเป็น switch แล้ว null เปลี่ยนความหมาย
การย้ายจาก instanceof มาเป็น switch ที่มี pattern เป็นการเปลี่ยนที่ดูปลอดภัยที่สุด
อย่างหนึ่งใน Java รุ่นใหม่
มันสั้นกว่า · compiler ตรวจให้มากกว่า · และผลลัพธ์เหมือนเดิม
ยกเว้นตอนที่ค่าที่รับเข้ามาเป็น null
สองรูปที่ให้ผลเหมือนกัน
หัวข้อที่มีชื่อว่า “สองรูปที่ให้ผลเหมือนกัน”record Pt(int x, int y) {}record Line(Pt a, Pt b) {}static int viaInstanceof(Object o) { if (o instanceof Line) { Line l = (Line) o; Pt a = l.a(); Pt b = l.b(); return a.x() + a.y() + b.x() + b.y(); } return 0;}static int viaPattern(Object o) { return switch (o) { case Line(Pt(int x1, int y1), Pt(int x2, int y2)) -> x1 + y1 + x2 + y2; default -> 0; };}ทั้งคู่คืน 10 เมื่อรับ new Line(new Pt(1, 2), new Pt(3, 4))
รันด้วย null แล้วผลไม่เหมือนกัน
หัวข้อที่มีชื่อว่า “รันด้วย null แล้วผลไม่เหมือนกัน”viaPattern(null) → java.lang.NullPointerExceptionviaInstanceof(null) → 0o instanceof Line เป็นเท็จเมื่อ o เป็น null · รูปเก่าจึงตกลงมาที่ return 0
แต่ switch ที่มี pattern โยน NullPointerException เมื่อค่าที่รับเข้ามาเป็น null
และไม่มี case null เขียนไว้
หลายคนอ่าน default ว่า “ทุกกรณีที่เหลือ” แล้วสรุปว่ามันรับ null ด้วย · มันไม่รับ
ถ้าต้องการให้รับ ต้องเขียนออกมาให้เห็น
return switch (o) { case null -> 0; case Line(Pt(int x1, int y1), Pt(int x2, int y2)) -> x1 + y1 + x2 + y2; default -> 0;};การบังคับให้เขียนออกมาเป็นการตัดสินใจของภาษา ไม่ใช่ความผิดพลาด
· มันทำให้คนอ่านเห็นว่าใครคิดเรื่อง null แล้วบ้าง
แต่ตอนย้าย code จากรูปเก่ามารูปใหม่ ความหมายเปลี่ยนโดยไม่มีอะไรเตือน · ไม่มี warning ไม่มี lint ที่เปิดมาเป็นค่าตั้งต้น
สองรูปนี้ไม่ได้แปลออกมาเหมือนกันด้วย
หัวข้อที่มีชื่อว่า “สองรูปนี้ไม่ได้แปลออกมาเหมือนกันด้วย”เปิดด้วย javap -c แล้วนับคำสั่ง
viaPattern | viaInstanceof | |
|---|---|---|
invokedynamic | 1 | 0 |
checkcast | 1 | 1 |
| bootstrap ที่เรียก | typeSwitch | — |
รูปใหม่ถาม bootstrap method ว่า ค่านี้ตรงกับ case ที่เท่าไร แล้วค่อยกระโดด
· ไม่ได้ไล่ instanceof ทีละอันเรียงลงมา
ผลคือเมื่อจำนวน case เพิ่มขึ้น รูปใหม่ไม่ได้ยาวขึ้นตามแบบเชิงเส้น
ส่วนรูปเก่ายาวขึ้นทุกครั้งที่เพิ่มเงื่อนไข
และมันอธิบายเรื่อง null ด้วย · การถาม typeSwitch ต้องถามด้วยค่าที่มีอยู่จริง
· null ไม่ใช่ค่าที่จับคู่ชนิดกับอะไรได้ ภาษาจึงบังคับให้คุณบอกว่าจะทำอย่างไรกับมัน
รายการตรวจก่อนย้าย
หัวข้อที่มีชื่อว่า “รายการตรวจก่อนย้าย”- มี
nullวิ่งผ่านตรงนี้ได้ไหม ถ้าไม่แน่ใจ ให้ถือว่าได้ - ถ้าได้ กิ่งไหนควรรับมัน เขียน
case nullออกมาให้เห็น · จะรวมกับกิ่งอื่นก็ได้ เช่นcase null, default -> - เทสต์ที่มีอยู่ ส่ง
nullเข้าไปหรือเปล่า ถ้าไม่ การย้ายนี้จะเขียวทั้งที่ความหมายเปลี่ยน
ข้อสุดท้ายคือข้อที่ทำให้ความผิดพลาดนี้หลุดขึ้น production ได้
· ไม่มีใครเขียนเทสต์ที่ส่ง null ให้ฟังก์ชันที่เคยรับ null ได้อย่างเงียบๆ อยู่แล้ว
ทั้ง bytecode และผลตอนรัน มาจากการแปลและรันจริง:
- วัดเมื่อ 2026-09-01 ·
javac 25.0.4.1·openjdk 25.0.4.1 2026-08-18 - รันซ้ำเองได้ด้วย
npm run verify:java-modernซึ่งนับคำสั่งจากผลของjavap -cแล้วรันทั้งสอง method ด้วยnull - ตัวอย่างรอบแรกของตัววัดตายทั้งโปรแกรมตอนรันด้วย
nullจึงรายงานว่าวัดไม่ได้ · เปลี่ยนให้จับ exception แล้วบันทึกชื่อชนิดแทน · ข้อที่โผล่มาจากตรงนั้นคือเนื้อของหน้านี้ - JEP 440 — Record Patterns (เข้าถึง 2026-09-01)
- JEP 441 — Pattern Matching for switch
(เข้าถึง 2026-09-01) — กติกาเรื่อง
case nullและเหตุที่defaultไม่รับnull - เรื่องนี้เป็นบทที่ 4 ของ คอร์ส Java 8 ถึง 25