4 คะแนน โดย GN⁺ 4 시간 전 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • JEP 401: Value Classes and Objects ไปถึงขั้นที่ถูกนำเข้าเป็น JDK preview จริงแล้ว
  • เป้าหมายหลักคือทำให้วัตถุ Java “เขียนโค้ดแบบคลาส แต่ทำงานเหมือน int” เพื่อลดต้นทุนของ object header, การจัดสรรบน heap, GC และการอ้างอิงทางอ้อมผ่าน pointer
  • value class ใน JDK 28 ยังเป็น reference type ที่เป็น null ได้ โดยยังไม่รวม non-null type, generics แบบ specialized และการเข้ารหัส 128 บิต และต้องใช้ --enable-preview
  • JVM สามารถ scalarize value object หรือทำ heap flattening ในฟิลด์และอาร์เรย์ได้ แต่ใน supertype อย่าง erased generic หรือ Object ก็อาจถูก materialize เป็นวัตถุบน heap ได้
  • นักพัฒนา Java ต้องสะท้อนความต่างระหว่าง identity กับ value ในการออกแบบโค้ด และผลกระทบจะต่อเนื่องไปถึง ==, synchronized, primitive wrapper, ประสิทธิภาพของอาร์เรย์ และการ specialized generics ในอนาคต

ขอบเขตของ Valhalla ที่จะเข้า JDK 28

  • วันที่ 15 มิถุนายน วิศวกร Oracle ชื่อ Lois Foltan ยืนยันการรวม JEP 401: Value Classes and Objects เข้าสู่ OpenJDK main repository และการตั้งเป้าไปที่ JDK 28
  • pull request ที่เกี่ยวข้องเพิ่มโค้ด มากกว่า 197,000 บรรทัด ครอบคลุม 1,816 ไฟล์
  • เนื่องจากขนาดการเปลี่ยนแปลงใหญ่มาก ระหว่างการรวมจึงมีการขอให้ committer คนอื่นชะลอคอมมิตขนาดใหญ่ไว้ชั่วคราว
  • JEP 401 เป็น ฟีเจอร์ preview ที่ปิดไว้โดยค่าเริ่มต้น
    • หากต้องการใช้ไวยากรณ์นี้ต้องใช้ --enable-preview
    • Brian Goetz วางกรอบว่านี่คือ “ส่วนแรกของ Valhalla”
  • JDK 28 มีกำหนดออกในเดือนมีนาคม 2027 และวางแผนรวมเข้ากับ mainline ราวเดือนกรกฎาคม 2026

ต้นทุนของโมเดลวัตถุ Java ที่ Valhalla ต้องการแก้

  • คำขวัญของ Valhalla คือ “codes like a class, works like an int
    • เป้าหมายคือให้ใช้คลาสปกติที่มีเมธอด การตรวจสอบใน constructor และชื่อฟิลด์ที่สื่อความหมายได้ ขณะเดียวกันก็ให้ JVM จัดการได้อย่างมีประสิทธิภาพเหมือน primitive
  • ใน Java หากไม่นับ primitive 8 ชนิด เกือบทุกอย่างเป็น reference type
    • ใน Point p = new Point(1, 2) ตัว p ไม่ใช่ point เอง แต่เป็น pointer ที่ชี้ไปยังวัตถุบน heap
    • ทุกครั้งที่อ่านฟิลด์ JVM ต้องไล่ตาม pointer
  • เมื่อจำนวนวัตถุเพิ่มขึ้น ต้นทุนจะพุ่งสูงอย่างรวดเร็ว
    • แต่ละวัตถุมี object header สำหรับข้อมูลอย่าง type และสถานะการ synchronize
    • วัตถุถูกจัดสรรบน heap และต่อมาจะกลายเป็นเป้าหมายของ GC
    • อาร์เรย์ Point หนึ่งล้านตัว แท้จริงแล้วประกอบด้วย pointer หนึ่งล้านตัวและวัตถุหนึ่งล้านชิ้นที่กระจายอยู่ทั่ว heap
  • เอกสาร “State of Valhalla” ของ Brian Goetz เรียกการจัดวางหน่วยความจำแบบนี้ว่า fluffy
    • สิ่งที่ Valhalla ต้องการคือการจัดวางแบบ dense ที่ข้อมูลวางเรียงชิดกัน

ช่องว่างกับฮาร์ดแวร์และข้อจำกัดของ escape analysis

  • เหตุผลที่การจัดวางหน่วยความจำแบบหนาแน่นสำคัญ คือช่องว่างความเร็วระหว่าง CPU กับหน่วยความจำ
    • ในปี 1995 ต้นทุนการเข้าถึงหน่วยความจำใกล้เคียงกับการคำนวณของ CPU
    • ปัจจุบัน CPU เร็วกว่า main memory หลายสิบเท่า และ cache เป็นตัวช่วยอุดช่องว่างนี้
  • โดยทั่วไป CPU จะอ่านหน่วยความจำเป็นหน่วย cache line ขนาด 64 ไบต์
    • หากข้อมูลเรียงกันแน่นและต่อเนื่อง จะดึงค่าที่มีประโยชน์ได้มากในครั้งเดียว
    • หากต้องไล่ pointer ไปยังวัตถุที่กระจัดกระจาย อาจเกิด cache miss ซึ่งช้ากว่า hit มาก
  • escape analysis ของ JVM สามารถลบการจัดสรรวัตถุบางส่วนออกได้
    • หากตัดสินได้ว่าวัตถุไม่ได้ “escape” ออกไปนอกช่วงโค้ดเฉพาะที่กำลังทำงาน ก็อาจไม่ต้องจัดสรรบน heap แต่คลี่ฟิลด์ออกเป็นตัวแปรหรือรีจิสเตอร์แทน
  • แต่ escape analysis มีความคาดเดาได้ต่ำและเปราะบาง
    • หากวัตถุถูกใส่ลงในฟิลด์ของคลาสอื่น ถูกเก็บในอาร์เรย์ ถูกส่งผ่านเมธอดที่ซับซ้อน หรือข้ามขอบเขตที่ JIT วิเคราะห์ไม่ได้ การปรับแต่งอาจหยุดทำงาน
    • เพียงรีแฟกเตอร์เล็กน้อย อัปเดต JDK หรือเปลี่ยนโครงสร้างโค้ด วัตถุก็อาจกลับไปอยู่บน heap อีกครั้ง
  • หากละทิ้งวัตถุเพื่อประสิทธิภาพ แล้วเข้ารหัสเองเป็น raw byte อย่าง r, g, b ก็อาจได้ความเร็วเพิ่มขึ้น แต่จะสูญเสีย ความปลอดภัย ความอ่านง่าย การตรวจสอบ และเมธอด ไป

การเริ่มต้นในปี 2014 และการเปลี่ยนจาก Q World ไปสู่ L World

  • Project Valhalla เริ่มต้นอย่างเป็นทางการในปี 2014
  • James Gosling เคยอธิบายมันไว้ในตอนนั้นว่า “six PhDs tied into a single knot”
  • ผู้สร้าง Java ต้องการ value type มาตั้งแต่ยุค Java 1.0 แต่ในปี 1995 ปัญหานี้ยากเกินกว่าจะทำได้ จึงต้องพับไปก่อน
  • เป้าหมายระยะแรกคือการกู้คืน ความสอดคล้อง ระหว่างโมเดลการเขียนโปรแกรมกับลักษณะประสิทธิภาพของฮาร์ดแวร์สมัยใหม่
    • แนวทางคือให้ผู้ใช้ประกาศ type ที่ flat และ dense ได้เองเหมือน primitive แต่ยังดูและทำงานเหมือนคลาสปกติ
  • prototype ช่วงแรกมุ่งไปในแนว Q World
    • มอง value type เป็นสิ่งที่แตกต่างจากวัตถุโดยพื้นฐาน และใช้ type descriptor, bytecode และ top type แยกต่างหาก
    • ทำให้ทั้งระบบ type ของ JVM ต้องมีสองแบบคู่ขนานกัน จึงเพิ่มความซับซ้อน
  • L World ที่ปรากฏขึ้นราวปี 2019 กลายเป็นจุดเปลี่ยน
    • value type ใช้ “L carrier” ร่วมกับ reference ปกติ
    • ทีมคาดว่าการรวมแบบนี้จะยาก แต่กลับทำงานได้โดยไม่ต้องแลกมาก และแก้ปัญหาหลายอย่างของ prototype ก่อนหน้า
  • ใน L World เกิดการแยกสำคัญขึ้น
    • โมเดลของ JVM กับโมเดลของภาษาไม่จำเป็นต้องซ้อนทับกัน 100%
    • JVM สามารถใช้โมเดล L World ขณะที่มอบโมเดลภาษาที่ใช้ง่ายกว่าสำหรับโปรแกรมเมอร์ได้
  • หลังจากนั้น งานถูกแบ่งออกเป็น สองระยะ คือ value class และ specialized generics

การเปลี่ยนแปลงของชื่อและโมเดล

  • คำศัพท์ของ Valhalla เปลี่ยนไปหลายครั้ง และไม่ใช่แค่การเปลี่ยนชื่อ แต่สะท้อนถึงการเปลี่ยนแปลงของโมเดลด้วย
  • คำเรียกในช่วงแรกคือ value types
    • ในเวลานั้นยังไม่ชัดเจนแน่ชัดว่าประเภทเหล่านี้คือสิ่งใดกันแน่
  • ราวปี 2019~2020 โมเดล inline classes เริ่มลงตัว
    • คลาสเดิมถูกจัดเป็น identity classes ส่วนคลาสใหม่ถูกแยกเป็น inline classes ที่ไม่มี identity
    • inline class เป็น final โดยปริยาย, ฟิลด์เป็น final และมีข้อจำกัดว่าไม่สามารถซิงโครไนซ์ได้
  • “State of Valhalla” ปี 2021 กล่าวถึง primitive classes และโมเดลแบบสอง projection
    • แนวคิดคือหนึ่ง type จะมีทั้ง value variant ที่แบนราบและไม่รองรับ null กับ reference variant ที่ยอมให้ null ได้
    • ยังมีการทดลองไวยากรณ์อย่าง Point.val / Point.ref และต่อมาคือ Point! / Point?
  • โมเดลนี้ทรงพลัง แต่มี ภาระด้านการทำความเข้าใจ สูง
    • โปรแกรมเมอร์ต้องเข้าใจรูปแบบสองชนิดของ type เดียวกันและจังหวะที่มีการแปลงอยู่เป็นประจำ
    • สุดท้ายจึงลดความเป็นคู่ตรงข้ามนี้ลงเพื่อทำให้โมเดลสำหรับผู้ใช้ง่ายขึ้น
  • ปัจจุบัน JEP 401 ใช้คำว่า value class และ value object
    • ประกาศ value class ด้วย modifier value
    • อินสแตนซ์คือ value object ที่ไม่มี identity
    • value class ยังคงเป็น reference type
  • non-nullability ถูกแยกออกเป็น JEP แบบทางเลือกต่างหากคือ Null-Restricted Value Class Types
    • ไม่ได้รวมอยู่ใน JDK 28
  • บทความเก่าที่อธิบายโมเดล “primitive classes” ก่อนหน้านี้อาจไม่ตรงกับมาตรฐาน OpenJDK ปัจจุบัน
  • ใน JEP 401 ยังมี JEP 402: Enhanced Primitive Boxing ที่เป็น preview มาพร้อมกันด้วย
    • แนวทางคือทำให้การแปลงระหว่าง primitive กับ wrapper ลื่นไหลขึ้น
    • ไม่ควรสมมติว่าทั้งหมดจะถูกรวมเข้ามาพร้อม JEP 401 ในรูปแบบที่เสร็จสมบูรณ์

โมเดล value class ใน JDK 28

  • value class ประกาศด้วย modifier value
value class USDCurrency implements Comparable<USDCurrency> {  
    private int cents; // implicitly final  
    public USDCurrency(int dollars, int cents) {  
        this.cents = dollars * 100 + cents;  
    }  
  
    public USDCurrency plus(USDCurrency that) {  
        return new USDCurrency(0, this.cents + that.cents);  
    }  
  
    // dollars(), cents(), compareTo(), toString()...  
}  
  • รองรับ value record ได้เช่นกัน
  • กฎหลักมีดังนี้
    • instance field ทั้งหมดเป็น final โดยปริยาย
    • method จะเป็น synchronized ไม่ได้
    • คลาสเป็น final โดยปริยาย
    • อนุญาตให้มีลำดับชั้นที่ประกอบด้วย value class และ abstract value class
    • ไม่สามารถสืบทอดคลาสที่มี identity ได้
    • สามารถ implement interface ได้
  • คุณสมบัติสำคัญที่สุดคือ ไม่มี identity
    • ออบเจ็กต์ทั่วไปแม้มีเนื้อหาเหมือนกัน ถ้าสร้าง new Point(1, 2) สองครั้งก็ยังเป็นคนละออบเจ็กต์
    • แต่ value object ไม่มี identity เช่นเดียวกับที่ค่า int 4 ไม่ได้มี “เลข 4 สองตัวที่ต่างกัน”

การเปลี่ยนแปลงของ ==, synchronized, และ null

  • สำหรับ value object, == จะไม่ใช่การเปรียบเทียบ identity แต่เป็นการตรวจสอบ substitutability
    • จะเปรียบเทียบแบบเรียกซ้ำว่ามาจากคลาสเดียวกันและมีค่าฟิลด์เหมือนกันหรือไม่
    • primitive field เปรียบเทียบระดับบิต ส่วน object field จะเปรียบเทียบต่อด้วย == อีกที
    • new USDCurrency(3,95) == new USDCurrency(3,95) จะได้ค่า true
  • อย่างไรก็ตาม == มองเข้าไปถึงสถานะภายใน ดังนั้นหากต้องการดูว่า “แทนข้อมูลเดียวกันหรือไม่” โดยทั่วไป equals มักเหมาะกว่า
  • value object ไม่มี identity ให้ใช้ซิงโครไนซ์
    • หากพยายามซิงโครไนซ์จะเกิด IdentityException
    • หากจำเป็นต้องตรวจสอบ identity แบบบังคับ สามารถใช้ Objects.requireIdentity และ Objects.hasIdentity ได้
  • value class ใน JDK 28 ยังคง เป็น null ได้
    • USDCurrency d = null; เป็นโค้ดที่ถูกต้อง
    • type ที่ห้าม null เป็น JEP ในอนาคตแยกต่างหาก และยังไม่มีใน JDK 28
  • non-nullability ไม่ใช่แค่ปัญหาเรื่องไวยากรณ์ แต่เป็น คันโยกด้านประสิทธิภาพ ที่เปิดทางไปสู่การ flatten ของ value class ที่มากขึ้น

Scalarization และ heap flattening

  • JEP 401 เปิดอิสระให้ JVM สามารถปรับแต่ง value object ให้เหมาะที่สุดได้
  • สคาลาร์ไรเซชัน (scalarization) คือเทคนิค JIT ที่แยก reference ของ value object ออกเป็นชุดของฟิลด์
    • แทนที่จะส่งพอยน์เตอร์ Color ก็อาจส่ง r, g, b แบบ byte พร้อมแฟล็กบอกว่าเป็น null หรือไม่
    • ต้นทุนการจัดสรรและ GC อาจหายไปได้
    • คล้าย escape analysis แต่คาดเดาได้มากกว่า และอาจใช้ข้ามขอบเขต method call ที่ไม่ได้ inline ได้
  • scalarization มีข้อจำกัด
    • ถ้าตัวแปรมี type เป็น Object ซึ่งเป็น supertype ของ value class หรือเป็น erased generic parameter โดยทั่วไปมักใช้ไม่ได้
    • ในกรณีนั้นออบเจ็กต์ต้องถูก materialize ลงบน heap
  • heap flattening คือการเข้ารหัสค่าฟิลด์ของ value object เป็น compact bit vector แล้วเขียนลงในฟิลด์หรือช่องของอาร์เรย์โดยตรง
    • ไม่จำเป็นต้องมีพอยน์เตอร์ไปยังตำแหน่งอื่นบน heap
    • ทำให้ได้ทั้งความหนาแน่นของข้อมูลและ locality
  • ข้อมูลที่ถูก flatten ต้องสามารถอ่านและเขียนแบบ atomic ได้เพื่อหลีกเลี่ยง tearing ระหว่าง concurrent access
    • บนแพลตฟอร์มทั่วไป ขนาดที่ “เล็กพอ” อาจอยู่ราว 64 บิตรวมแฟล็ก null
    • value class ขนาดเล็กอาจถูก flatten ได้ดี แต่แม้มีเพียงสองฟิลด์ int หรือมีแค่ double ตัวเดียว ก็อาจไม่พอดีกับขนาดการเขียนแบบ atomic และกลายเป็นออบเจ็กต์บน heap แบบปกติ
  • ในอนาคต การเข้ารหัส 128 บิตและ type แบบ null-restricted อาจทำให้ flatten value class ที่ใหญ่กว่านี้ได้

ผลกระทบต่อ Boxing, wrapper, และอาร์เรย์

  • เมื่อเปิด preview, primitive wrapper class อย่าง Integer, Long, Double เองก็จะกลายเป็น value class
    • box จะสูญเสีย identity ทำให้ JVM สามารถ scalarize และ flatten ได้
    • Integer[] จะเข้าใกล้ประสิทธิภาพของ int[] มากขึ้น และมุ่งไปสู่การลด boxing overhead อย่างมาก
  • JEP 402: Enhanced Primitive Boxing ขยายการแปลงระหว่าง primitive กับ box ให้มากขึ้น
    • เปิดทางไปสู่รูปแบบอย่าง List<int> แต่ยังเป็นงานแยกที่กำลังพัฒนาให้สมบูรณ์
  • ผลกระทบจะเห็นชัดที่สุดในอาร์เรย์
    • Color[] แบบเดิมอาจกลายเป็นพอยน์เตอร์หนึ่งล้านตัวและออบเจ็กต์หนึ่งล้านตัวที่กระจัดกระจายอยู่บน heap
    • Color[] ที่เป็น value class อาจกลายเป็น contiguous block ที่เก็บค่าสีต่อเนื่องกันโดยตรง
    • CPU สามารถอ่านค่าหลายค่าแบบต่อเนื่องได้ทีละ cache line

ความต่างก่อนและหลังผ่านตัวอย่าง Point[]

  • ตัวอย่าง general class ก่อนยุค Valhalla มีดังนี้
final class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • อาร์เรย์นี้เก็บ พอยน์เตอร์ หนึ่งล้านตัว
    • พอยน์เตอร์แต่ละตัวชี้ไปยังออบเจ็กต์ Point แยกกันที่อยู่สักแห่งบนฮีป
    • แต่ละออบเจ็กต์มี object header นอกเหนือจาก int สองตัว
    • เวลาไล่วนต้องอ่านพอยน์เตอร์ กระโดดไปยังแอดเดรสนั้น แล้วค่อยอ่านฟิลด์
  • ตัวอย่าง value class หลัง Valhalla เป็นดังนี้
value class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • ความต่างของโค้ดมีแค่คำว่า value คำเดียว แต่การจัดวางหน่วยความจำเปลี่ยนไป
    • JVM สามารถเก็บค่าของแต่ละ point แบบอัดแน่นอยู่ภายในอาร์เรย์ได้โดยตรง
    • ไม่มี header ต่อ element และไม่มีพอยน์เตอร์
    • หากยึดตาม int สองตัวของ x, y ก็สามารถวางเรียงต่อเนื่องเป็น 8 ไบต์พร้อม null flag ที่เป็นไปได้
  • ด้านการดูแลรักษาก็ยังคงดีอยู่
    • Point ยังเป็น class ที่มีชื่อ มี constructor มีการตรวจสอบความถูกต้อง และมี method
    • จึงไม่ต้องแยกเป็น int[] xs, int[] ys แล้วคอยจับคู่ index ให้ตรงกัน

เหตุใด specialized generics จึงยังเป็นสิ่งที่เหลืออยู่

  • Java generics ถูกทำงานด้วย type erasure
    • List<String> และ List<Integer> เป็น List เดียวกันใน runtime
    • type parameter T จะถูก erasure เป็น Object
  • erasure เป็นทางเลือกที่ตั้งใจไว้เพื่อเพิ่ม generics เข้าไปโดยไม่ทำให้โค้ด Java เดิมพัง
    • แม้จะเปลี่ยน class ที่ไม่ใช่ generic ให้เป็น generic ก็ยังไม่ทำให้ source file และ compiled class เดิมเสียหาย
  • Valhalla กับ erasure ขัดกันในแง่ประสิทธิภาพ
    • ถ้าใส่ value object ลงใน List<Point> เนื่องจาก T ถูก erasure เป็น Object ออบเจ็กต์นั้นจึงต้องถูก materialize บนฮีป
    • ข้อดีจากการ flatten ใน Point[] อาจหายไปเมื่ออยู่ใน ArrayList<Point>
  • แผนแก้ไขมีสองขั้น
    • Universal Generics: ทำให้ type variable รองรับ value type ได้ในระดับภาษา
      • ยังคงใช้ erasure อยู่
      • เพราะฟิลด์ T เริ่มต้นจากค่า null โดยปริยาย จึงอาจเกิด compiler warning เรื่อง “null pollution”
      • เมื่อแก้ warning ได้แล้ว API ก็จะเข้าใกล้ความเป็น specialization-ready
    • Specialized Generics: ในระดับ JVM จะสร้าง class layout ที่ specialized ตาม concrete type argument แต่ละแบบ
      • ในคำศัพท์ของโปรเจ็กต์มีแนวคิดเรื่อง species และ type restriction ที่เกี่ยวข้อง
      • ต้องถึงขั้นนี้ก่อน ArrayList<Point> จึงจะใช้หน่วยความจำแบบ flat ได้จริง
  • ใน JDK 28 ยังไม่มี full specialized generics
    • การที่คอลเลกชัน, stream และ API จะ flat และไม่ต้อง allocation เมื่อทำงานบน value type ยังเป็นงานของ release ในอนาคต

สิ่งที่มีและไม่มีใน JDK 28

  • สิ่งที่จะเข้ามาใน JDK 28 มีดังนี้
    • การประกาศ value class และ value record
    • การย้าย value-based class เดิมใน JDK เช่น primitive wrapper ไปเป็น value class
    • การ scalarize และ flatten สำหรับ class ที่ตรงตามเงื่อนไข
    • boxing ที่มีต้นทุนต่ำลง
  • สิ่งที่ไม่มีใน JDK 28 มีดังนี้
    • null-restricted types
    • full specialized generics
    • การเข้ารหัส 128 บิต
    • JEP 402 ที่สมบูรณ์เต็มที่
  • เนื่องจากเป็น preview feature ไวยากรณ์และพฤติกรรมอาจเปลี่ยนในแต่ละ release ตาม feedback
  • JDK 28 ไม่ใช่ LTS
    • LTS ถัดไปมีแนวโน้มจะเป็น JDK 29 ในเดือนกันยายน 2027
    • หลายบริษัทอาจได้พบ Valhalla ที่เสถียรแล้วใน LTS แต่ preview ของ JDK 28 คือจุดเริ่มต้นของ feedback loop จากโค้ดจริง

การเปลี่ยนแปลงที่จะเกิดขึ้นกับ ecosystem และโค้ด

  • สำหรับโลก Java ประสิทธิภาพสูง Valhalla คือเส้นทางในการจัดการข้อมูลแบบหนาแน่นโดยไม่ต้องละทิ้ง abstraction
    • เช่น งานด้านประมวลผลข้อมูล, vector computation, ML, game development, การเงิน และ codec
  • framework และ library สามารถเริ่ม migration ไปสู่ value-based class ได้
  • โค้ดที่พึ่งพา identity อาจเจอความต่างของพฤติกรรม
    • == บน value object จะไม่ใช่การเทียบแอดเดรส แต่เป็นการเทียบ substitutability
    • synchronized บน value object จะนำไปสู่ IdentityException
  • แม้ Integer จะกลายเป็น value class แต่ในกรณีส่วนใหญ่ไบนารีก็ยังลิงก์ต่อได้
    • compilation error แบบใหม่จะเกิดในกรณีที่พยายามทำ synchronization บนชนิดเหล่านี้
    • == หรือ synchronized(someInteger) ที่อาศัย identity ของ Integer อาจได้รับผลกระทบ
  • early-access build ใช้งานได้ที่ jdk.java.net/valhalla

สรุปคำถามที่พบบ่อย

  • value class ไม่เหมือนกับ record
    • record คือการเลือกให้เนื้อหาถูกกำหนดเป็น component
    • value คือการเลือกสละ identity
    • สามารถมีได้ครบทุกแบบคือ class ปกติ, record, value class และ value record
  • value object เปรียบเทียบด้วย == ได้
    • แต่ความหมายไม่ใช่การเทียบแอดเดรส แต่เป็น substitutability
    • สำหรับความเท่าเทียมของข้อมูลที่แทนอยู่ โดยทั่วไป equals มักเหมาะกว่า
  • value class ใน JDK 28 ยังเป็น null ได้
    • non-nullable type เป็น JEP ในอนาคต
    • เรื่องนี้สำคัญต่อการ flatten ของ value class ที่มีขนาดใหญ่กว่าด้วย
  • ArrayList<Point> แบบ flat ที่เร็วขึ้นยังมาไม่ถึง
    • เพราะ type erasure ทำให้ออบเจ็กต์ใน generic collection ต้องถูก materialize บนฮีป
    • ใน JDK 28 กรณีเด่นที่ flatten ทำงานโดยตรงคือ field ของ value type และอาร์เรย์อย่าง Point[]
  • escape analysis ไม่สามารถทดแทนทุกอย่างได้
    • ถ้าออบเจ็กต์หลุดออกไปยัง field, array หรือพ้นขอบเขตที่การวิเคราะห์มองเห็น การเพิ่มประสิทธิภาพอาจพังได้
    • การ scalarize ของ value object คาดเดาได้มากกว่าและข้ามขอบเขตของ method call ไปได้ไกลกว่า
  • Valhalla ทั้งโครงการจะค่อย ๆ ขยายผ่านหลาย release
    • JDK 28 คือ preview แรกของ value class
    • specialized generics, null-restricted types และการเข้ารหัส 128 บิต เป็นงานที่จะตามมาใน release อนาคต

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

 
click 1 시간 전

virtual thread ของ Project Loom ที่ออกรีลีสมาตลอดช่วงเวลานานนั้นใช้งานสะดวก และช่วยจัดการหลายอย่างให้ในระดับ JVM runtime ทำให้นักพัฒนามีเรื่องที่ต้องกังวลน้อยลง
หวังว่า Project Valhalla เองก็จะได้ออกรีลีสสุดท้ายมาในความรู้สึกแบบเป็นของฟรีใกล้เคียงกันแบบนั้นนะ

 
GN⁺ 4 시간 전
ความเห็นจาก Hacker News
  • เขาบอกว่าความต่างด้านหน่วยความจำเป็นเรื่องพื้นฐาน แต่ก็อดสงสัยไม่ได้ว่าบทความนี้ผ่านการตรวจทานมาดีพอหรือเปล่า
    เมื่อกี้เพิ่งอธิบายไม่ใช่หรือว่าวัตถุที่มี representation เกิน 64 บิต จะไม่ถูก flatten ลง heap? Point ในตัวอย่างมีจำนวนเต็ม 32 บิต 2 ตัวบวกกับ null flag อีก อย่างน้อยก็ 65 บิตแล้ว
    จากถ้อยคำอย่าง “อาจมี null flag ได้” กับประโยคสั้น ๆ ที่เน้นย้ำตามมา ทำให้รู้สึกเหมือน AI พยายามสร้างประโยคเน้นแล้วหลุดประเด็นไป และบล็อก "[IMAGE: the same Point[] array in two variants..." ตรงกลางก็น่าเสียดาย

    • ประโยคอย่าง “ไม่มี header ต่อ element ไม่มี pointer ไม่ต้องกระโดดไล่ตาม heap ไปทั่ว” มีกลิ่น สำนวน AI ชัดมาก เลยดูเหมือนงานเขียนแบบขี้เกียจ
      ใช้ AI มาช่วยเขียนก็ไม่เป็นไร แต่ถ้าไม่ใส่เสียงของตัวเองลงไป ก็ไม่มีเหตุผลให้อ่าน
      https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
    • หัวข้อดูน่าสนใจมากจนอยากอ่าน และฉันยังพอรับภาพที่สร้างด้วย AI ได้
      แต่พอผ่านไปไม่กี่ย่อหน้าก็ชัดเจนว่ามันเป็นบทความที่ ผ่าน LLM หรือวิธีที่แย่กว่านั้น
      จะเป็น technical blog หรืออะไรก็ตาม ได้โปรดอย่าให้ AI เขียนแทนเลย ไม่มีใครอยากอ่านอะไรแบบนั้น
    • มีค่าที่เป็นไปได้ตั้ง 18446744073709551616 ค่า แต่กลับกันไว้ให้ null สัก 1 ค่าไม่ได้งั้นหรือ? :)
      วันนี้เพิ่งรู้ว่าใน Rust มี NonZeroU64 และเมื่อใช้ร่วมกับ Optional ก็ได้พฤติกรรมที่ต้องการโดยใช้แค่ 64 บิตต่อรายการ
      https://doc.rust-lang.org/std/num/type.NonZeroU64.html
    • ใช้ AI มากเกินไปจนเห็นได้ชัด อ่านไป 2 ย่อหน้าก็พอเลย
    • ประโยคที่ว่า “วัตถุที่มี representation เกิน 64 บิตจะไม่ถูก flatten ลง heap” เป็นเรื่องของ commit แรกเริ่ม
      อย่างที่ระบุชัดใน JEP นี่เป็นเพียงผลลัพธ์ชิ้นแรกของฟีเจอร์ขนาดใหญ่มาก และเหมือนฟีเจอร์ Java ช่วงหลัง ๆ มันกำลังถูกส่งมาเป็นชิ้น ๆ
      เป้าหมายสุดท้ายแน่นอนว่าคือ flatten ค่าที่ใหญ่กว่านี้ด้วย และกลไกนั้นก็มีอยู่ใน JVM แล้ว ที่เหลือคือการแสดงเจตนาในระดับภาษาให้ชัดว่า “อนุญาตให้มี tearing ได้”
  • ยอมรับในความทุ่มเทของงานที่ใส่เข้าไปใน Valhalla จริง ๆ แต่ตีความว่า “โมเดลนี้ทรงพลังแต่หนักทางความคิด” นั้นเห็นด้วยได้ยาก
    การบอกว่าตัวแปรหนึ่งเป็น null ไม่ได้ไม่ใช่ ความแตกต่างที่สร้างภาระทางความคิด โดยเฉพาะเมื่อทุกอย่างระบุไว้อย่างชัดเจนอยู่แล้ว
    ท่าทีแบบ “ยอมเสียเพดานประสิทธิภาพเพื่อทำให้โมเดลผู้ใช้ง่ายขึ้น” อาจกลับกลายเป็นว่ากำลังมอบความเรียบง่ายให้ผู้ใช้ก็ได้
    type system ของภาษาโปรแกรมมีไว้เพื่อให้หลักประกันที่สะดวกแก่ผู้พัฒนาบน CPU ที่สุดท้ายแล้วจัดการได้แค่ตัวเลข ไม่จำเป็นต้องลดหลักประกันด้านความปลอดภัยที่เป็นทางเลือกลงเพียงเพราะบอกว่า “ซับซ้อนเกินไป”
    ทั้งที่ไปถึงจุดที่ยอมรับแล้วด้วยซ้ำว่า “โมเดลของภาษาและโมเดลของ JVM ไม่จำเป็นต้องซ้อนทับกัน 100%”

    • ไม่ค่อยมั่นใจว่า Java จะกำหนดทิศทางของตัวเองได้ดีแค่ไหน
      ระบบการกำกับดูแลของ Java ดูอ่อนแอ และยิ่งตัดกับฝั่ง .NET ที่ตัดสินใจได้ค่อนข้างถูกตั้งแต่แรก
      ทุกวันนี้ยังสงสัยเลยว่าภายใน Oracle นั้น Java ยังมีคุณค่าหรือได้รับความสนใจอยู่ไหม บริษัทตอนนี้ดูเหมือนธุรกิจดาต้าเซ็นเตอร์/คอมพิวต์ที่มีภาระ legacy กับหนี้ก้อนโตพ่วงอยู่
      บางทีก็รู้สึกว่าส่วนที่ยังทำเงินให้ Oracle อาจเหลือแค่ทีมกฎหมายกับธุรกิจตัดหญ้าเท่านั้น
    • ที่บ่นอยู่นี่ไม่ได้บ่นใส่ภาษา Java แต่บ่นใส่บล็อกเกอร์มากกว่าหรือ?
      และ ตัวบ่งชี้ null ก็จะตามมาเหมือนกัน: https://openjdk.org/jeps/8303099
      เพียงแต่ต้องค่อย ๆ ปล่อยออกมาเท่านั้น แค่ PR นี้ที่เพิ่ม value class/object เข้าไปก็มีขนาดถึง 200,000 บรรทัดแล้ว
    • value type ที่ไม่รับ null แค่ถูกเลื่อนไปไว้ใน JEP ถัดไปเท่านั้น
      ดูไม่ได้หมายความว่าทำไม่ได้ แต่เป็นเรื่องของการกินช้างทีละคำ
      ถึงอย่างนั้นก็ต้องยอมรับว่าพวกเขากำลังแทะขาข้างนี้มานานพอสมควรแล้ว
    • nullable ก็เป็นแค่อีกสถานะการบรรทุกหนึ่งใน railway-oriented programming
      ถ้าเป็นแนวคิดที่แก้กันได้มาตั้งแต่ปี 2012 ก็ไม่มีเหตุผลต้องยัดรสชาติของ state หลายแบบเข้าไปในภาษาโดยตรง รางก็มีแค่ไปทาง A หรือ B แล้วรถไฟจะไปทางไหนก็ขึ้นอยู่กับของที่บรรทุก
      ถ้าแนวคิดบางอย่างโผล่มาหายไปแล้วก่อสงครามภาษาซ้ำ ๆ นั่นคือสัญญาณว่ามีความต้องการอยู่จริง แต่ภาษายังจัดการความต้องการนั้นได้ไม่ดี หรือจัดการได้แต่สร้างภาระทางความคิด
      อย่างพวก Value, Errorstates, Null, IoExceptions, WeirdOsStatesNeededToHandleUpstairs
      https://fsharpforfunandprofit.com/rop/
      ถ้าจะพูดแบบ Monty Python ก็คือ ไปต่อกันได้แล้ว
    • สิ่งที่พูดถึงตรงนี้ไม่ใช่ null safety แต่เป็นเรื่อง reference/value projection คล้าย Integer/int
      ทีม Valhalla เลือกที่จะไม่ให้แต่ละ type มีทั้ง projection แบบมี identity และไม่มี identity แต่ทำให้ value type ไม่มี identity ไปเลย ดังนั้น Integer กับ int จึงกลายเป็นคำพ้องความหมายกัน
      การจัดวางหน่วยความจำจะถูกกำหนดอัตโนมัติตามบริบทและการตัดสินใจด้าน optimization ดังนั้นความหมายของ == สำหรับ wrapper พื้นฐานอย่าง Integer ก็เปลี่ยนไปด้วย และจะไม่ขึ้นกับอีกต่อไปว่าใช้ “reference projection” หรือ “value projection”
      สิ่งที่เกิดขึ้นตรงนี้ไม่ใช่การลดหลักประกันด้านความปลอดภัยแบบเลือกได้เพราะมองว่า “เป็นภาระทางความคิด”
  • ในคอมเมนต์ HN ที่เกี่ยวกับ Java/JVM มักเห็นสิ่งเดิมซ้ำ ๆ คือมีคนจำนวนมากอย่างน่าประหลาดที่ยังมีภาพจำเก่า ๆ ของ JVM หรือ Java อยู่ แต่แทบไม่รู้เลยว่า ทุกวันนี้มันเป็นอย่างไร
    JVM ในปี 2026 เป็นผู้ล่าที่แข็งแรงมาก แน่นอนว่ามีจุดบกพร่องอยู่บ้าง แต่รากฐานนั้นดีเยี่ยมอย่างยิ่ง

    • บน HN เป็นเรื่องยากที่จะได้เห็นคำชื่นชม JVM และที่นี่มันมักถูกมองว่าเป็นเทคโนโลยีตกยุค
      ในงานผมใช้ Java 26 รุ่นล่าสุดกับฟีเจอร์พรีวิว โดยเฉพาะ StructuredConcurrency และมันยอดเยี่ยมมาก แม้จะเคยใช้ Haskell กับ Python ในบริษัทก่อน ๆ มาก็ตาม ก็ไม่เสียใจเลยสักนิด
    • หลายคนยังต้องดูแล Java monolith ที่เริ่มต้นขึ้นตั้งแต่ยุคที่ Java บูมในช่วงทศวรรษ 2000 และยังต้องรันอยู่บน Java 8
      ส่วนตัวผมรู้จักฟีเจอร์ใหม่ ๆ ที่ออกมาในช่วงไม่กี่ปีนี้ แต่ในงานจริง Java ยังติดอยู่กับอดีตแบบตามตัวอักษร
  • คอมเมนต์จำนวนมากที่นี่ค่อนข้างไม่ยุติธรรม เมื่อเทียบกับงานยอดเยี่ยมที่กำลังทำอยู่ตอนนี้และ JEP ที่น่าตื่นเต้นยิ่งกว่าที่ยังจะออกมา
    ถ้าเปรียบ Java เป็นเด็ก ช่วงสองสามปีแรกก็เติบโตมากับพ่อแม่ที่รักใคร่ (Sun) แล้วหลังจากนั้นก็ถูกโยนไปกองไว้ในโรงรถรวมกับเด็กคนอื่น ๆ และถูกปล่อยปละละเลยโดยผู้ปกครองใจร้าย (Oracle)
    มันถูกทอดทิ้งและไม่ได้รับความรักมาจนถึง JDK 8 ดังนั้นโดยพื้นฐานแล้วจึงเป็นการวิ่งไล่ตามคนอื่น
    คำพูดอย่าง “เพิ่งจะมี struct หรือ value type เอาตอนนี้” ก็จริงอยู่ แต่เป็นเพราะการเติบโตของมันถูกขัดขวางโดยกระบวนการองค์กรขนาดใหญ่ที่เป็นระบบราชการและเป็นปฏิปักษ์ ตอนนี้มันเป็นอิสระแล้วและได้รับความรักผ่านครอบครัว OpenJDK
    ต่อจากนี้เราก็จะยังได้เพลิดเพลินกับความสุขของการ เขียนครั้งเดียว แล้วนำไปปล่อยได้ทุกที่

    • จะชอบ Oracle หรือไม่ก็ตาม นั่นไม่ใช่คำบรรยายประวัติศาสตร์ Java ที่ถูกต้อง
      มันใกล้เคียงกับการที่พ่อแม่ที่รักใคร่เลี้ยงดูมา แต่เพราะปัญหาทางการเงินจึงต้องฝากไว้กับครอบครัวอุปถัมภ์ และที่นั่นมันถูกปล่อยปละละเลย
      จากนั้นพ่อแม่ใหม่ที่เปี่ยมความรักอย่าง Oracle ก็รับไปอุปการะ และ Java ก็ผลิบานเป็นผู้ใหญ่ที่แข็งแรงและมั่นคง
      Oracle นี่เองที่ทำให้ OpenJDK กลายเป็น implementation อ้างอิงและทำให้แพลตฟอร์มกลายเป็นโอเพนซอร์สอย่างสมบูรณ์ รวมถึงนำเครื่องมือที่เดิมเป็น proprietary อย่าง JFR และ Mission Control มาโอเพนซอร์สด้วย
      ยังรักษาสมาชิกดั้งเดิมของทีมภาษาไว้ได้จำนวนมาก ซึ่งหาได้ยากมากในการเข้าซื้อกิจการแบบนี้ และ Java ก็พัฒนาขึ้นอย่างมากทั้งฝั่งภาษาและรันไทม์
    • ช่วงที่ Java ถูกปล่อยปละละเลยจริง ๆ คือไม่กี่ปีสุดท้ายภายใต้ Sun
      Oracle ผลักดัน Java ไปข้างหน้าด้วยความเร็วที่ไม่เคยมีมาก่อน ขณะเดียวกันก็ยังคงความเข้ากันได้ย้อนหลังไว้เป็นส่วนใหญ่
      มีคนบอกว่า .NET “ทำถูกตั้งแต่แรก” แต่ถ้านั่นหมายถึงความแยกขาดและการเขียนใหม่ของ .NET Framework/.NET Core/.NET ก็ฟังไม่ขึ้นแม้แต่ในบริบทของการคุยนี้เอง .NET มีโอกาสเรียนรู้จาก Java แต่ก็ยังมีส่วนที่ทำพัง
      MySQL ก็เหมือนกัน ที่เว็บนี้ชอบพูดกันว่ามัน “ตายแล้ว” แต่สำหรับคนที่รู้จริง มันฟื้นคืนชีพภายใต้ Oracle
    • คำว่า “Oracle ปล่อย Java ทิ้ง” กับ “มันถูกปล่อยทิ้งจนถึง JDK 8” เป็นคำพูดที่ขัดแย้งกันเอง
      Java เวอร์ชันสุดท้ายภายใต้ Sun ออกในปี 2006, Oracle ซื้อ Sun ในปี 2010, JDK 7 ออกในปี 2011 และ JDK 8 ออกในปี 2014
      โดยรวมแล้วทีมแทบจะเหมือนเดิม ความแตกต่างใหญ่ที่สุดคือ Oracle เป็นฝ่ายยุติการปล่อยทิ้งและอัดเงินเพิ่มเข้าไป นั่นจึงเป็นเหตุผลที่ความเร็วของ Java เพิ่มขึ้นหลังการเข้าซื้อกิจการ
      มีการพูดถึงว่าเป็น “การวิ่งไล่ตาม” แต่ก็ยังไม่ชัดว่ากำลังไล่ตามใครอยู่ ภาษาเดียวที่ได้รับความนิยมพอ ๆ กันหรือมากกว่า Java ก็มีแค่ JS/TS กับ Python
      คนที่บอกว่า Java ตามหลัง มักเอาไปเทียบกับภาษาที่โดยรวมทำได้แย่กว่า Java มาก ผู้คนที่ชอบฟีเจอร์เฉพาะบางอย่างมักมองข้ามว่าภาษาที่มีฟีเจอร์นั้นกลับซบเซาไม่ใช่เพราะฟีเจอร์นั้น แต่ทั้ง ๆ ที่มีฟีเจอร์นั้นต่างหาก
      ฝั่งผู้บริหารชอบสิ่งที่ออกได้เร็ว ขณะที่ผู้นำทางเทคนิคซึ่งมีมาตั้งแต่ยุค Sun กลับยืนยันว่าควรทำอย่างระมัดระวัง ช้า ๆ แต่ให้ถูกต้อง
      ผมเข้าใจบรรยากาศที่ว่าตอนนี้ Java ไม่ได้ฮิตเท่าปี 2003 แต่ช่วงนั้นเป็นยุคที่ทั้ง Java และระบบนิเวศซอฟต์แวร์โดยรวมมีความเป็นเอกภาพผิดปกติ และก่อนหน้านั้นหรือหลังจากนั้นก็ไม่เคยรวมศูนย์ขนาดนั้นอีกเลย
    • บอกว่า “เขียนครั้งเดียว รันได้ทุกที่” แต่ก็ใช้ไม่ได้กับเบราว์เซอร์, iOS หรือระบบฝังตัว
      ตอนนี้เทคโนโลยีที่เป็น เขียนครั้งเดียว รันได้ทุกที่ จริง ๆ คือ WebAssembly ถึงคราวของ JVM แล้ว และมันแพ้ไปแล้ว
    • ถ้าจะต่อยอดอุปมาอีกหน่อย Java ไม่ได้แค่ถูกโยนเข้ากองในโรงรถ แต่ยังถูกใช้เป็นเครื่องมือยื่นฟ้อง Google เรียกค่าเลี้ยงดูหลายพันล้านดอลลาร์ จนสุดท้ายกลายเป็นช่องทางรับเงินสดไปด้วย
      ถึงอย่างนั้นผมก็ยังไม่ถึงกับเรียกว่า Java “เติบโตชะงัก” มันมีการตัดสินใจหลายอย่าง บางอย่างสมเหตุสมผล บางอย่างไม่ใช่ และการตัดสินใจแบบนั้นแก้ย้อนหลังได้ยากมาก
      ดู C++ ก็ได้ เรื่องความเข้ากันได้ย้อนหลังแบบครึ่ง ๆ กลาง ๆ กับ C สำหรับผมคือภาระหนักอึ้งที่แก้ไม่ออกยาว 150 ฟุต และหลายเวอร์ชันหลัง C++11 ก็เป็นความพยายามทำให้ภาระนั้นพอทนได้ขึ้นเล็กน้อย
      บน JVM การจัดการ value class ทั้งหมดเป็น L-type เดียวเหมือน primitive type ดูเป็นทางออกที่ค่อนข้างสะอาดสำหรับปัญหาที่ยาก
      ท้ายที่สุดทั้งหมดนี้สืบเนื่องมาจากการตัดสินใจในยุค Java 2 ที่จะทำ generics ด้วย type erasure เพื่อรักษาความเข้ากันได้ย้อนหลัง และ C3 ก็เห็นผลลัพธ์นั้นแล้วเลือกจะไม่เดินตามเส้นทางนี้
  • แค่เรื่อง วิวัฒนาการของ value type ใน Java อย่างเดียว ก็น่าจะเขียนเป็นเทคโนโลยีทริลเลอร์ได้ทั้งเล่ม
    ผมอ่าน mailing list และดูวิดีโอที่เกี่ยวข้องทั้งหมดแล้ว และประทับใจมากกับกระบวนการที่รวมงานออกแบบให้กลายเป็นสิ่งที่ยังดูเป็น Java อยู่เสมอ
    ขณะเดียวกันก็เจาะลึกลงไปอย่างละเอียดกว่ามากว่า value type หมายถึงอะไร และสามารถทำ optimization แบบไหนได้ ที่ตรงไหนบ้าง

    • การเปลี่ยนแปลงทางไวยากรณ์มีแค่การเพิ่ม value เพียงคำเดียว
  • สำหรับ value class นั้น == จะกลายเป็นว่าทำงานคล้าย memcmp() โดยพฤตินัย
    ตรงนี้ค่อนข้างน่าเสียดาย เพราะมันทำลาย การห่อหุ้ม และเปิดเผยรายละเอียดของการติดตั้งใช้งาน
    โค้ดฝั่งไคลเอนต์สามารถแตกแขนงตามได้ว่าค่าที่ให้มาถูกแทนภายในอย่างไร ในบางแง่แล้วมันแย่กว่าการเปรียบเทียบเอกลักษณ์เสียอีก เพราะอย่างน้อยการเปรียบเทียบเอกลักษณ์ก็ไม่ได้เปิดเผยสถานะภายใน

    • value type เป็นแนวคิดที่ห่างไกลมากจากการคิดเชิงวัตถุแบบ “สิ่งมีชีวิตกล่องดำมหัศจรรย์”
      มันไม่ใช่การทำ object-oriented แบบดั้งเดิมในรูปแบบใหม่ แต่เป็นภาษาที่ถือกำเนิดจากอุดมการณ์เชิงวัตถุ ก้าวไปอีกขั้นสู่โลก หลัง object-oriented
    • ถ้าก้อนข้อมูลมีสถานะภายใน ก็แปลว่าก้อนข้อมูลนั้นเองออกแบบผิด
      ทางฝั่ง Java เองก็น่าจะคิดเรื่องตัด padding ออกจากการเปรียบเทียบ หรือบังคับให้ไบต์ padding เป็น 0 ได้สบาย ๆ
      เรื่องนี้ควรใช้กับสตริงด้วย สตริงก็น่าจะยังถูกจัดสรรบนฮีปต่อไป และการ memcmp พอยน์เตอร์ภายใน “struct” แบบใหม่ก็คือการเปรียบเทียบเอกลักษณ์ตรง ๆ นั่นเอง
    • แก่นสำคัญของ value class คือมันไม่ควรห่อหุ้มสถานะ กล่าวคือเป็นเพียงที่เก็บข้อมูลที่โปร่งใสทั้งหมด
    • ถ้าไม่เคยใช้ Java ในงานจริง อาจไม่รู้สึกถึงความหมายที่แท้จริงของการเปลี่ยนแปลงนี้ นี่คือ การเปลี่ยนแบบทำให้ของเดิมพัง ที่ Java แทบไม่ค่อยทำ
      Java แยกการตรวจเอกลักษณ์ของอ็อบเจ็กต์ออกจากการตรวจความเท่ากัน == โดยพื้นฐานจะดูว่าพอยน์เตอร์สองตัวเป็นตัวเดียวกันหรือไม่ ส่วนความเท่ากันเป็นแนวคิดเชิงอัตวิสัยที่อิงกับอินเทอร์เฟซอย่าง equals/hashCode
      ดังนั้น new Integer(1000) == new Integer(1000) เมื่อก่อนเป็น false แต่ตอนนี้จะเป็น true, และ new Integer(1000).equals(new Integer(1000)) เป็น true ขณะที่ new Integer(10) == new Long(10) เมื่อก่อนเป็น false แต่ตอนนี้กลายเป็นข้อผิดพลาดขณะคอมไพล์
      ใน Java แบบเดิม จำนวนเต็มที่ไม่เกินค่าหนึ่งมักถูกแทนด้วยชนิดที่ canonicalized และถ้าจำไม่ผิดน่าจะอยู่แถว ๆ 128 นั่นจึงเป็นสาเหตุที่ 10 กับ 1000 ให้ผลต่างกัน
      ตอนนี้ดูเหมือนว่าการเปรียบเทียบข้างต้นจะมีการ unboxing โดยนัย การที่ Integer/Long เมื่อก่อนเป็น false แต่ตอนนี้เป็นข้อผิดพลาดตอนคอมไพล์ บ่งชี้ชัดว่ามี unboxing เข้ามาเกี่ยวข้อง
      กับตัวแปรอาจยังได้พฤติกรรมแบบเดิมอยู่ก็ได้
      ไม่ว่าอย่างไร เมื่อ value class สูญเสียเอกลักษณ์ไป == ก็จะเปลี่ยนจากความเท่ากันของพอยน์เตอร์เป็นความเท่ากันระดับบิต หวังว่าพวกเขาจะแก้กรณีมุมต่าง ๆ เหล่านี้ได้ แต่ในเชิงเทคนิคนี่คือการเปลี่ยนแบบทำให้ของเดิมพัง
  • เข้าใจเจตนาของ value class แต่การติดตั้งมีข้อบกพร่อง
    โค้ดต่อไปนี้จะพิมพ์อะไร? Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);
    จนถึงตอนนี้คำตอบชัดเจน แต่พอเพิ่ม value class เข้ามา คำตอบจะต่างกันตามว่า Point เป็น value class หรือ reference class ดังนั้นดีไซน์นี้จึงทำลาย ความอ่านง่าย
    นี่เป็นการละเมิดหลักความสม่ำเสมอ Weinberg อธิบายใน The Psychology of Computer Programming ว่าความสม่ำเสมอคือหลักทางจิตวิทยาที่ผู้ใช้คาดหวังว่าสิ่งที่ดูคล้ายกันจะทำงานคล้ายกัน และสิ่งที่ดูต่างกันจะทำงานต่างกัน
    ถ้าภาษาโปรแกรมยอมให้ไวยากรณ์สองแบบที่จุดใช้งานดูแทบเหมือนกันแต่มีพฤติกรรมทางความหมายต่างกัน ภาระทางการรับรู้ของผู้อ่านจะสูงขึ้น ต้องคอยดูการประกาศชนิดหรือพึ่งเครื่องมือเพื่อรู้ว่าการกำหนดค่า ความเท่ากัน เอกลักษณ์ และการแก้ไข เปลี่ยนแปลง ทำงานแบบอ็อบเจ็กต์อ้างอิงทั่วไปหรือแบบค่า
    ถ้าบังคับให้ใช้คีย์เวิร์ด value ไม่ใช่แค่ตอนประกาศ แต่ตอนใช้งานด้วย ก็น่าจะแก้ได้ เช่นเขียนเป็น value Point a = new Point(10, 10);

    • ดูจากเป้าหมายของ JEP 401 แล้ว เรื่องนี้เป็นไปไม่ได้ เพราะ value class มีไว้เพื่อให้ผู้พัฒนาสามารถเลือกโมเดลการเขียนโปรแกรมสำหรับ ข้อมูลไม่เปลี่ยนแปลง ได้
      https://openjdk.org/jeps/401
    • ถ้าอ่านบทความ บรรทัดที่ 3 เป็นข้อผิดพลาดทางไวยากรณ์ ฟิลด์ทั้งหมดของ value type เป็น final
      แน่นอนว่าแค่ดูสี่บรรทัดนั้นอย่างเดียวไม่มีทางรู้ได้ว่าจะเกิดแบบนั้นไม่ได้ แต่ปัญหานี้ก็มีอยู่แล้วตอนนี้ ถ้า Point เป็น record ก็เกิดเรื่องเดียวกัน
    • คำสั่ง a.x = 100; ไม่น่าจะใช้ได้ เพราะ record type เป็น immutable
      ดังนั้นสถานการณ์ที่กังวลจึงไม่ควรเกิดขึ้นได้
    • ถึงอย่างนั้นมันก็ยังดูพร่า ๆ อยู่
      ถ้าเขียนเป็น value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x); จะชัดกว่ามากว่ามีการโคลน/คัดลอกเกิดขึ้น และการแก้ไขฟิลด์ของอ็อบเจ็กต์หนึ่งจะไม่ส่งผลกับฟิลด์ของอีกอ็อบเจ็กต์
  • รู้แหละว่าในโลก Java การยอมรับการมีอยู่ของ .NET เหมือนจะเสียมารยาท แต่ก็สงสัยว่านี่ต่างจาก struct ของ .NET อย่างไร
    ถ้าดูผ่าน ๆ เรื่อง value type, generic specialization และ boxing ก็ดูเหมือนเลือกแนวทางเดียวกัน

    • ใน C# มีหลุมพรางอยู่ไม่น้อยจริง ๆ และเป้าหมายของ Java คือทำให้เรื่องนี้ชัดเจนขึ้นโดยเจตนา
      ถ้า C# ค่อนข้างคัดลอก C จากมุมมองระดับล่างมา Java ฝั่งนี้กลับเริ่มจากระดับสูงแล้ววิเคราะห์ละเอียดว่าข้อจำกัดแบบไหนให้ข้อดีอะไร
      ในภาษาอื่น การแบ่ง struct/class เป็นแบบสองขั้ว แต่ Java เปิดให้ควบคุมได้ละเอียดกว่าเพื่อสะท้อนความหมายของโดเมนต้นทาง
      และยังพบว่า struct มี ปืนลั่นใส่เท้า อยู่หลากหลาย โดยเฉพาะในบริบทการทำงานขนาน
    • ในบทความมีส่วนที่พูดถึงเรื่องนั้นอยู่
      ส่วนตัวมองว่า struct ของ C/C# แก้ไขได้และส่งต่อด้วยการคัดลอก ขณะที่ value class แก้ไขไม่ได้และถูกส่งต่อเป็นค่า
      ใน Java คิดว่าไม่น่าจะทำ stack allocation ได้
    • ในเชิงความสามารถก็ไม่ได้ต่างกัน และ Java แค่กำลังไล่ตามแนวปฏิบัติเก่า ๆ ให้ทันเท่านั้น
      การแบ่งแบบเทียมว่า “struct ของ C# มีเอกลักษณ์และการเปลี่ยนแปลงได้ จึงต้องกำหนดความหมายของการกำหนดค่าและการส่งต่อแบบคัดลอกอย่างแม่นยำ ทำให้โมเดลหนักขึ้นสำหรับโปรแกรมเมอร์และให้อิสระกับรันไทม์น้อยลง” ดูไม่ค่อยสอดคล้องกับสิ่งที่อธิบายเท่าไร
      มันอาจไม่มีเอกลักษณ์ในความหมายของ reference semantics แบบคลาส Java แต่ในความหมายที่เป็นโครงสร้างหน่วยความจำเฉพาะ ณ ที่อยู่หนึ่ง ๆ มันก็ยังมีเอกลักษณ์อยู่ดี นี่ออกจะเป็นการเล่นคำกับศัพท์ของ Java มากกว่า
  • เชิงอรรถ 6 ที่ว่า “ต่างจาก struct ของ C# อย่างไร” นั้นไม่แม่นยำ
    พอเห็นว่าบทความใส่ ภาพที่สร้างด้วย AI มาเต็มไปหมด ก็อดสงสัยไม่ได้ว่าทั้งงานเขียน หรืออย่างน้อยกระบวนการค้นคว้า ก็คงปนภาพหลอนมาด้วยเยอะเหมือนกัน

  • บทความค่อนข้างคลุมเครือและเขียนให้ดราม่าไปหน่อย แต่โชคดีที่เอกสารต้นฉบับอ่านค่อนข้างง่าย
    หน้าระดับบนสุด: https://openjdk.org/projects/jdk/28/spec/
    สถานะ JEP: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
    อยากให้มีใครสักคนช่วยติดตามพัฒนาการที่เกี่ยวข้องของ C#, Swift, Java และ Rust ให้หน่อย ทุกภาษาต่างก็แข่งกันเพื่อไล่ให้ทันฮาร์ดแวร์ และผมคิดว่าพวกมันก็กำลังส่งอิทธิพลถึงกันและกันด้วย
    ส่วนตัวผมกังวลว่าการเปลี่ยนแปลงเหล่านี้จะส่งผลต่อ การแชร์หน่วยความจำผ่าน FFI อย่างไร