- 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
- บทความเก่าที่อธิบายโมเดล “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 ความคิดเห็น
virtual thread ของ Project Loom ที่ออกรีลีสมาตลอดช่วงเวลานานนั้นใช้งานสะดวก และช่วยจัดการหลายอย่างให้ในระดับ JVM runtime ทำให้นักพัฒนามีเรื่องที่ต้องกังวลน้อยลง
หวังว่า Project Valhalla เองก็จะได้ออกรีลีสสุดท้ายมาในความรู้สึกแบบเป็นของฟรีใกล้เคียงกันแบบนั้นนะ
ความเห็นจาก Hacker News
เขาบอกว่าความต่างด้านหน่วยความจำเป็นเรื่องพื้นฐาน แต่ก็อดสงสัยไม่ได้ว่าบทความนี้ผ่านการตรวจทานมาดีพอหรือเปล่า
เมื่อกี้เพิ่งอธิบายไม่ใช่หรือว่าวัตถุที่มี representation เกิน 64 บิต จะไม่ถูก flatten ลง heap?
Pointในตัวอย่างมีจำนวนเต็ม 32 บิต 2 ตัวบวกกับ null flag อีก อย่างน้อยก็ 65 บิตแล้วจากถ้อยคำอย่าง “อาจมี null flag ได้” กับประโยคสั้น ๆ ที่เน้นย้ำตามมา ทำให้รู้สึกเหมือน AI พยายามสร้างประโยคเน้นแล้วหลุดประเด็นไป และบล็อก
"[IMAGE: the same Point[] array in two variants..."ตรงกลางก็น่าเสียดายใช้ AI มาช่วยเขียนก็ไม่เป็นไร แต่ถ้าไม่ใส่เสียงของตัวเองลงไป ก็ไม่มีเหตุผลให้อ่าน
https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
แต่พอผ่านไปไม่กี่ย่อหน้าก็ชัดเจนว่ามันเป็นบทความที่ ผ่าน LLM หรือวิธีที่แย่กว่านั้น
จะเป็น technical blog หรืออะไรก็ตาม ได้โปรดอย่าให้ AI เขียนแทนเลย ไม่มีใครอยากอ่านอะไรแบบนั้น
18446744073709551616ค่า แต่กลับกันไว้ให้ null สัก 1 ค่าไม่ได้งั้นหรือ? :)วันนี้เพิ่งรู้ว่าใน Rust มี
NonZeroU64และเมื่อใช้ร่วมกับOptionalก็ได้พฤติกรรมที่ต้องการโดยใช้แค่ 64 บิตต่อรายการhttps://doc.rust-lang.org/std/num/type.NonZeroU64.html
อย่างที่ระบุชัดใน JEP นี่เป็นเพียงผลลัพธ์ชิ้นแรกของฟีเจอร์ขนาดใหญ่มาก และเหมือนฟีเจอร์ Java ช่วงหลัง ๆ มันกำลังถูกส่งมาเป็นชิ้น ๆ
เป้าหมายสุดท้ายแน่นอนว่าคือ flatten ค่าที่ใหญ่กว่านี้ด้วย และกลไกนั้นก็มีอยู่ใน JVM แล้ว ที่เหลือคือการแสดงเจตนาในระดับภาษาให้ชัดว่า “อนุญาตให้มี tearing ได้”
ยอมรับในความทุ่มเทของงานที่ใส่เข้าไปใน Valhalla จริง ๆ แต่ตีความว่า “โมเดลนี้ทรงพลังแต่หนักทางความคิด” นั้นเห็นด้วยได้ยาก
การบอกว่าตัวแปรหนึ่งเป็น null ไม่ได้ไม่ใช่ ความแตกต่างที่สร้างภาระทางความคิด โดยเฉพาะเมื่อทุกอย่างระบุไว้อย่างชัดเจนอยู่แล้ว
ท่าทีแบบ “ยอมเสียเพดานประสิทธิภาพเพื่อทำให้โมเดลผู้ใช้ง่ายขึ้น” อาจกลับกลายเป็นว่ากำลังมอบความเรียบง่ายให้ผู้ใช้ก็ได้
type system ของภาษาโปรแกรมมีไว้เพื่อให้หลักประกันที่สะดวกแก่ผู้พัฒนาบน CPU ที่สุดท้ายแล้วจัดการได้แค่ตัวเลข ไม่จำเป็นต้องลดหลักประกันด้านความปลอดภัยที่เป็นทางเลือกลงเพียงเพราะบอกว่า “ซับซ้อนเกินไป”
ทั้งที่ไปถึงจุดที่ยอมรับแล้วด้วยซ้ำว่า “โมเดลของภาษาและโมเดลของ JVM ไม่จำเป็นต้องซ้อนทับกัน 100%”
ระบบการกำกับดูแลของ Java ดูอ่อนแอ และยิ่งตัดกับฝั่ง .NET ที่ตัดสินใจได้ค่อนข้างถูกตั้งแต่แรก
ทุกวันนี้ยังสงสัยเลยว่าภายใน Oracle นั้น Java ยังมีคุณค่าหรือได้รับความสนใจอยู่ไหม บริษัทตอนนี้ดูเหมือนธุรกิจดาต้าเซ็นเตอร์/คอมพิวต์ที่มีภาระ legacy กับหนี้ก้อนโตพ่วงอยู่
บางทีก็รู้สึกว่าส่วนที่ยังทำเงินให้ Oracle อาจเหลือแค่ทีมกฎหมายกับธุรกิจตัดหญ้าเท่านั้น
และ ตัวบ่งชี้ null ก็จะตามมาเหมือนกัน: https://openjdk.org/jeps/8303099
เพียงแต่ต้องค่อย ๆ ปล่อยออกมาเท่านั้น แค่ PR นี้ที่เพิ่ม value class/object เข้าไปก็มีขนาดถึง 200,000 บรรทัดแล้ว
ดูไม่ได้หมายความว่าทำไม่ได้ แต่เป็นเรื่องของการกินช้างทีละคำ
ถึงอย่างนั้นก็ต้องยอมรับว่าพวกเขากำลังแทะขาข้างนี้มานานพอสมควรแล้ว
ถ้าเป็นแนวคิดที่แก้กันได้มาตั้งแต่ปี 2012 ก็ไม่มีเหตุผลต้องยัดรสชาติของ state หลายแบบเข้าไปในภาษาโดยตรง รางก็มีแค่ไปทาง A หรือ B แล้วรถไฟจะไปทางไหนก็ขึ้นอยู่กับของที่บรรทุก
ถ้าแนวคิดบางอย่างโผล่มาหายไปแล้วก่อสงครามภาษาซ้ำ ๆ นั่นคือสัญญาณว่ามีความต้องการอยู่จริง แต่ภาษายังจัดการความต้องการนั้นได้ไม่ดี หรือจัดการได้แต่สร้างภาระทางความคิด
อย่างพวก
Value,Errorstates,Null,IoExceptions,WeirdOsStatesNeededToHandleUpstairshttps://fsharpforfunandprofit.com/rop/
ถ้าจะพูดแบบ Monty Python ก็คือ ไปต่อกันได้แล้ว
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 เป็นผู้ล่าที่แข็งแรงมาก แน่นอนว่ามีจุดบกพร่องอยู่บ้าง แต่รากฐานนั้นดีเยี่ยมอย่างยิ่ง
ในงานผมใช้ Java 26 รุ่นล่าสุดกับฟีเจอร์พรีวิว โดยเฉพาะ
StructuredConcurrencyและมันยอดเยี่ยมมาก แม้จะเคยใช้ Haskell กับ Python ในบริษัทก่อน ๆ มาก็ตาม ก็ไม่เสียใจเลยสักนิดส่วนตัวผมรู้จักฟีเจอร์ใหม่ ๆ ที่ออกมาในช่วงไม่กี่ปีนี้ แต่ในงานจริง Java ยังติดอยู่กับอดีตแบบตามตัวอักษร
คอมเมนต์จำนวนมากที่นี่ค่อนข้างไม่ยุติธรรม เมื่อเทียบกับงานยอดเยี่ยมที่กำลังทำอยู่ตอนนี้และ JEP ที่น่าตื่นเต้นยิ่งกว่าที่ยังจะออกมา
ถ้าเปรียบ Java เป็นเด็ก ช่วงสองสามปีแรกก็เติบโตมากับพ่อแม่ที่รักใคร่ (Sun) แล้วหลังจากนั้นก็ถูกโยนไปกองไว้ในโรงรถรวมกับเด็กคนอื่น ๆ และถูกปล่อยปละละเลยโดยผู้ปกครองใจร้าย (Oracle)
มันถูกทอดทิ้งและไม่ได้รับความรักมาจนถึง JDK 8 ดังนั้นโดยพื้นฐานแล้วจึงเป็นการวิ่งไล่ตามคนอื่น
คำพูดอย่าง “เพิ่งจะมี struct หรือ value type เอาตอนนี้” ก็จริงอยู่ แต่เป็นเพราะการเติบโตของมันถูกขัดขวางโดยกระบวนการองค์กรขนาดใหญ่ที่เป็นระบบราชการและเป็นปฏิปักษ์ ตอนนี้มันเป็นอิสระแล้วและได้รับความรักผ่านครอบครัว OpenJDK
ต่อจากนี้เราก็จะยังได้เพลิดเพลินกับความสุขของการ เขียนครั้งเดียว แล้วนำไปปล่อยได้ทุกที่
มันใกล้เคียงกับการที่พ่อแม่ที่รักใคร่เลี้ยงดูมา แต่เพราะปัญหาทางการเงินจึงต้องฝากไว้กับครอบครัวอุปถัมภ์ และที่นั่นมันถูกปล่อยปละละเลย
จากนั้นพ่อแม่ใหม่ที่เปี่ยมความรักอย่าง Oracle ก็รับไปอุปการะ และ Java ก็ผลิบานเป็นผู้ใหญ่ที่แข็งแรงและมั่นคง
Oracle นี่เองที่ทำให้ OpenJDK กลายเป็น implementation อ้างอิงและทำให้แพลตฟอร์มกลายเป็นโอเพนซอร์สอย่างสมบูรณ์ รวมถึงนำเครื่องมือที่เดิมเป็น proprietary อย่าง JFR และ Mission Control มาโอเพนซอร์สด้วย
ยังรักษาสมาชิกดั้งเดิมของทีมภาษาไว้ได้จำนวนมาก ซึ่งหาได้ยากมากในการเข้าซื้อกิจการแบบนี้ และ Java ก็พัฒนาขึ้นอย่างมากทั้งฝั่งภาษาและรันไทม์
Oracle ผลักดัน Java ไปข้างหน้าด้วยความเร็วที่ไม่เคยมีมาก่อน ขณะเดียวกันก็ยังคงความเข้ากันได้ย้อนหลังไว้เป็นส่วนใหญ่
มีคนบอกว่า .NET “ทำถูกตั้งแต่แรก” แต่ถ้านั่นหมายถึงความแยกขาดและการเขียนใหม่ของ .NET Framework/.NET Core/.NET ก็ฟังไม่ขึ้นแม้แต่ในบริบทของการคุยนี้เอง .NET มีโอกาสเรียนรู้จาก Java แต่ก็ยังมีส่วนที่ทำพัง
MySQL ก็เหมือนกัน ที่เว็บนี้ชอบพูดกันว่ามัน “ตายแล้ว” แต่สำหรับคนที่รู้จริง มันฟื้นคืนชีพภายใต้ Oracle
Java เวอร์ชันสุดท้ายภายใต้ Sun ออกในปี 2006, Oracle ซื้อ Sun ในปี 2010, JDK 7 ออกในปี 2011 และ JDK 8 ออกในปี 2014
โดยรวมแล้วทีมแทบจะเหมือนเดิม ความแตกต่างใหญ่ที่สุดคือ Oracle เป็นฝ่ายยุติการปล่อยทิ้งและอัดเงินเพิ่มเข้าไป นั่นจึงเป็นเหตุผลที่ความเร็วของ Java เพิ่มขึ้นหลังการเข้าซื้อกิจการ
มีการพูดถึงว่าเป็น “การวิ่งไล่ตาม” แต่ก็ยังไม่ชัดว่ากำลังไล่ตามใครอยู่ ภาษาเดียวที่ได้รับความนิยมพอ ๆ กันหรือมากกว่า Java ก็มีแค่ JS/TS กับ Python
คนที่บอกว่า Java ตามหลัง มักเอาไปเทียบกับภาษาที่โดยรวมทำได้แย่กว่า Java มาก ผู้คนที่ชอบฟีเจอร์เฉพาะบางอย่างมักมองข้ามว่าภาษาที่มีฟีเจอร์นั้นกลับซบเซาไม่ใช่เพราะฟีเจอร์นั้น แต่ทั้ง ๆ ที่มีฟีเจอร์นั้นต่างหาก
ฝั่งผู้บริหารชอบสิ่งที่ออกได้เร็ว ขณะที่ผู้นำทางเทคนิคซึ่งมีมาตั้งแต่ยุค Sun กลับยืนยันว่าควรทำอย่างระมัดระวัง ช้า ๆ แต่ให้ถูกต้อง
ผมเข้าใจบรรยากาศที่ว่าตอนนี้ Java ไม่ได้ฮิตเท่าปี 2003 แต่ช่วงนั้นเป็นยุคที่ทั้ง Java และระบบนิเวศซอฟต์แวร์โดยรวมมีความเป็นเอกภาพผิดปกติ และก่อนหน้านั้นหรือหลังจากนั้นก็ไม่เคยรวมศูนย์ขนาดนั้นอีกเลย
ตอนนี้เทคโนโลยีที่เป็น เขียนครั้งเดียว รันได้ทุกที่ จริง ๆ คือ WebAssembly ถึงคราวของ JVM แล้ว และมันแพ้ไปแล้ว
ถึงอย่างนั้นผมก็ยังไม่ถึงกับเรียกว่า 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()โดยพฤตินัยตรงนี้ค่อนข้างน่าเสียดาย เพราะมันทำลาย การห่อหุ้ม และเปิดเผยรายละเอียดของการติดตั้งใช้งาน
โค้ดฝั่งไคลเอนต์สามารถแตกแขนงตามได้ว่าค่าที่ให้มาถูกแทนภายในอย่างไร ในบางแง่แล้วมันแย่กว่าการเปรียบเทียบเอกลักษณ์เสียอีก เพราะอย่างน้อยการเปรียบเทียบเอกลักษณ์ก็ไม่ได้เปิดเผยสถานะภายใน
มันไม่ใช่การทำ object-oriented แบบดั้งเดิมในรูปแบบใหม่ แต่เป็นภาษาที่ถือกำเนิดจากอุดมการณ์เชิงวัตถุ ก้าวไปอีกขั้นสู่โลก หลัง object-oriented
ทางฝั่ง Java เองก็น่าจะคิดเรื่องตัด padding ออกจากการเปรียบเทียบ หรือบังคับให้ไบต์ padding เป็น 0 ได้สบาย ๆ
เรื่องนี้ควรใช้กับสตริงด้วย สตริงก็น่าจะยังถูกจัดสรรบนฮีปต่อไป และการ
memcmpพอยน์เตอร์ภายใน “struct” แบบใหม่ก็คือการเปรียบเทียบเอกลักษณ์ตรง ๆ นั่นเอง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);https://openjdk.org/jeps/401
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# ค่อนข้างคัดลอก C จากมุมมองระดับล่างมา Java ฝั่งนี้กลับเริ่มจากระดับสูงแล้ววิเคราะห์ละเอียดว่าข้อจำกัดแบบไหนให้ข้อดีอะไร
ในภาษาอื่น การแบ่ง struct/class เป็นแบบสองขั้ว แต่ Java เปิดให้ควบคุมได้ละเอียดกว่าเพื่อสะท้อนความหมายของโดเมนต้นทาง
และยังพบว่า struct มี ปืนลั่นใส่เท้า อยู่หลากหลาย โดยเฉพาะในบริบทการทำงานขนาน
ส่วนตัวมองว่า struct ของ C/C# แก้ไขได้และส่งต่อด้วยการคัดลอก ขณะที่ value class แก้ไขไม่ได้และถูกส่งต่อเป็นค่า
ใน Java คิดว่าไม่น่าจะทำ stack allocation ได้
การแบ่งแบบเทียมว่า “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 อย่างไร