แยกส่วนใน `case` และข้อที่มันไม่เหมือนของเดิม
บทที่แล้วปิดท้ายด้วย case Card c -> "บัตรลงท้าย " + c.last4();
เรียกชื่อตัวแปรแล้วเรียก method ต่ออีกทอด · บทนี้ตัดทอดนั้นทิ้ง แล้วดูว่าแลกอะไรไป
สองรูปที่ให้ผลเหมือนกัน
หัวข้อที่มีชื่อว่า “สองรูปที่ให้ผลเหมือนกัน”ฝั่งหนึ่งเจ็ดบรรทัด อีกฝั่งหนึ่งบรรทัด
record Pt(int x, int y) {}record Line(Pt a, Pt b) {}รูปเดิมที่ทุกคนเขียนมาตั้งแต่ Java 8
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;}รูปใหม่ที่ใช้ record patternrecord patternการแยกช่องข้อมูลของ `record` ออกมาเป็นตัวแปรในบรรทัดเดียวกับที่จับคู่ชนิด · ซ้อนกันได้หลายชั้น และ compiler ตรวจชนิดของทุกชั้นให้
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))
deconstructiondeconstructionการรื้อของออกเป็นชิ้นส่วนตามรูปที่มันถูกประกอบขึ้น · ใช้ได้เฉพาะกับชนิดที่ประกาศรูปของตัวเองไว้ ซึ่งใน Java คือ `record` เท่านั้น ทำงานได้เพราะ record ประกาศรูปของตัวเองไว้ในหัว
· ชนิดที่ไม่ได้ประกาศรูป แยกส่วนแบบนี้ไม่ได้ · นี่คือเหตุผลที่ Java แยกส่วนได้เฉพาะ record
javap เห็นอะไร
หัวข้อที่มีชื่อว่า “javap เห็นอะไร”รูปใหม่ไม่ได้เป็นน้ำตาลเคลือบของรูปเก่า มันแปลออกมาคนละแบบ
viaPattern | viaInstanceof | |
|---|---|---|
invokedynamic | 1 | 0 |
checkcast | 1 | 1 |
| bootstrap ที่เรียก | typeSwitch | — |
รูปใหม่ถาม bootstrap method ว่า ค่านี้ตรงกับ case ที่เท่าไร แล้วค่อยกระโดด
· ไม่ได้ไล่ instanceof ทีละอันเรียงลง
ผลที่ตามมาคือเมื่อจำนวน case เพิ่มขึ้น รูปใหม่ไม่ได้ยาวขึ้นตามแบบเชิงเส้น
ส่วนรูปเก่ายาวขึ้นทุกครั้งที่เพิ่มเงื่อนไข
flowchart LR O["ค่าที่รับเข้ามา"] O --> P["viaPattern"] O --> I["viaInstanceof"] P --> BS["invokedynamic<br/>typeSwitch"] --> IDX["ได้เลขของ case ที่ตรง"] --> J["กระโดดไปกิ่งนั้น"] I --> C1["instanceof Line"] --> C2["แปลงชนิด"] --> C3["เรียก getter ทีละชั้น"]
คำบรรยายภาพ: รูปหนึ่งถามครั้งเดียวว่าตรง case ไหน อีกรูปไล่เงื่อนไขเรียงลงมา
ข้อที่ต่างกันจริง และมันไม่ได้อยู่ในความสั้น
หัวข้อที่มีชื่อว่า “ข้อที่ต่างกันจริง และมันไม่ได้อยู่ในความสั้น”รันทั้งสองรูปด้วย
nullแล้วผลไม่เหมือนกัน
viaPattern(null) → java.lang.NullPointerExceptionviaInstanceof(null) → 0o instanceof Line เป็นเท็จเมื่อ o เป็น null · รูปเก่าจึงตกลงมาที่ return 0
แต่ switch ที่มี pattern โยน NullPointerException เมื่อค่าที่รับเข้ามาเป็น null
และไม่มี case null เขียนไว้ · แม้จะมี default อยู่ก็ตาม
หลายคนอ่านว่า 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 จากรูปเก่ามารูปใหม่ ความหมายเปลี่ยนโดยไม่มีอะไรเตือน · นี่คือข้อเดียวในบทนี้ที่ทำให้ของที่เคยทำงานอยู่ พังได้
type patterntype patternการเขียน `case ชนิด ชื่อ` แล้วได้ตัวแปรที่แปลงชนิดแล้วมาใช้ทันที · แทนการเขียน `instanceof` แล้วแปลงชนิดซ้ำอีกบรรทัด กับ record pattern ต่างกันอย่างไร
หัวข้อที่มีชื่อว่า “type pattern กับ record pattern ต่างกันอย่างไร”ตัวแรกจับชนิด ตัวที่สองจับชนิดแล้วรื้อออกเป็นชิ้น
case Line l -> l.a().x();case Line(Pt a, Pt ignored) -> a.x();case Line(Pt(int x, int ignoredY), Pt ignoredB) -> x;สามบรรทัดนี้จับของอย่างเดียวกัน · ต่างกันที่รื้อลึกแค่ไหน
รื้อลึกขึ้นแล้วได้สองอย่าง · ชื่อที่สื่อความหมายกว่า l.a().x()
· และ compiler ตรวจชนิดของทุกชั้นให้ ไม่ใช่แค่ชั้นนอกสุด
รื้อลึกเกินไปก็อ่านยากขึ้น · ไม่มีกติกาตายตัว แต่มีเกณฑ์ที่ใช้ได้ คือ รื้อถึงชั้นที่คุณใช้ค่าจริง แล้วหยุด
สิ่งที่บทนี้ยังไม่ทำ
หัวข้อที่มีชื่อว่า “สิ่งที่บทนี้ยังไม่ทำ”สามอย่างที่ยังไม่มี และแต่ละอย่างมาถึงเมื่อมีเหตุผลให้ต้องมี
- ยังไม่ได้ใช้ชนิดพื้นฐานใน pattern
case int iยังเป็น preview ในรุ่นนี้ · บทที่ 7 พูดเรื่องราคาของ preview - ยังไม่ได้ใช้
whenการกรองเพิ่มในกิ่งเดียวกันเป็นของที่มีอยู่แล้ว แต่ไม่ใช่เรื่องที่บทนี้วัด - ยังไม่ได้แตะ
Streamซึ่งเป็นที่ที่ Java 8 ให้ของไว้เยอะที่สุด · บทที่ 5
สรุปก่อนไปต่อ
หัวข้อที่มีชื่อว่า “สรุปก่อนไปต่อ”บทนี้ให้ของที่สั้นกว่า และเตือนเรื่องที่ไม่มีใครเตือน
record pattern แปลออกมาคนละแบบกับ instanceof ที่เขียนมือ · มันถาม typeSwitch
ครั้งเดียวแล้วกระโดด แทนการไล่เงื่อนไขเรียงลงมา
และมันรับ null ไม่เหมือนกัน · รูปเก่าคืนค่าปกติ รูปใหม่โยน exception
· ถ้าย้าย code โดยไม่รู้ข้อนี้ ของที่เคยทำงานจะพังเฉพาะทางที่มี null วิ่งผ่าน
บทหน้าเราไป Stream ซึ่งเป็นของที่ Java 8 ให้ไว้ครบที่สุด
— แล้วดูว่า Gatherers เติมอะไรที่ map กับ filter ทำไม่ได้
ทั้ง bytecode และผลตอนรัน มาจากการแปลและรันจริง:
- วัดเมื่อ 2026-09-01 ·
javac 25.0.4.1·openjdk 25.0.4.1 2026-08-18 - รันซ้ำเองได้ด้วย
npm run verify:java-modernซึ่งนับinvokedynamicกับcheckcastจากผลของjavap -cของทั้งสอง method แล้วรันทั้งคู่ด้วยnull - ตัวอย่างรอบแรกของ probe ตายทั้งโปรแกรมตอนรันด้วย
nullจึงรายงานว่าวัดไม่ได้ · เปลี่ยนให้จับ exception แล้วบันทึกชื่อชนิดแทน ข้อที่โผล่มาจากตรงนั้นกลายเป็นแกนของบทนี้ - JEP 440 — Record Patterns (เข้าถึง 2026-09-01)
- JEP 441 — Pattern Matching for switch
(เข้าถึง 2026-09-01) — กติกาเรื่อง
case nullและเหตุที่defaultไม่รับnull
เช็กความเข้าใจ — บทที่ 4
ข้อ 1 / 3ย้ายจาก `if (o instanceof Line)` มาเป็น `switch` ที่มี pattern และมี `default` · แล้วส่ง `null` เข้าไป จะเกิดอะไรขึ้น