โหมดมืด
บทที่ 10 — Modern Java (Records, Sealed, Pattern Matching, Virtual Threads, Java 25 LTS)
บทสุดท้าย — feature ใหม่ ๆ ของ Java 17/21/25 ที่ทำให้โค้ดสั้น + ปลอดภัยขึ้น
หลังจบบท คุณจะ:
- ใช้
recordแทน class แบบ boilerplate - ใช้
sealedจำกัด inheritance + pattern matching - ใช้ pattern matching ใน
instanceof+switch - รู้จัก text block, switch expression, var
- เข้าใจ virtual threads (Java 21+) ระดับพื้นฐาน
- รู้จัก Java 25 LTS features:
- Instance main + compact source (เขียน main แบบสั้น ไม่ต้อง class wrap /
static) - module import (
import module java.baseแทน import ทีละตัว) - Stream Gatherers (custom intermediate op สำหรับ Stream)
- Scoped Values (ค่าแชร์ระหว่างเรียก method แบบ immutable + มี scope ชัด)
- Instance main + compact source (เขียน main แบบสั้น ไม่ต้อง class wrap /
📖 ศัพท์ในตารางข้างล่าง — รู้ก่อน:
- stable = ใช้ใน production ได้เลย ไม่มีคำเตือน
- preview = ของทดลอง ต้องเปิด flag
--enable-previewตอน compile/run และอาจถูกถอน/แก้ในรุ่นถัดไป — อย่าใช้ใน production- withdrawn = ถูกถอนทิ้งหลังพิจารณาแล้วไม่ดี อาจกลับมาในรูปแบบใหม่
- JEP = Java Enhancement Proposal เลขโครงการ Java (เช่น JEP 512 = ข้อเสนอเลข 512) — ดูทาง openjdk.org/jeps/N ได้
📅 Timeline ของ feature ในบทนี้ (สถานะ ณ ปี 2026, Java 25 LTS):
Feature Stable ตั้งแต่ สถานะ var Java 10 stable Text Block Java 15 stable Record Java 16 stable Pattern matching instanceofJava 16 stable Sealed class/interface Java 17 stable Switch expression Java 14 stable Pattern matching switchJava 21 stable Record pattern Java 21 stable Virtual Threads Java 21 stable Sequenced Collections Java 21 stable (บทที่ 7) Stream Gatherers Java 24 stable Scoped Values Java 25 stable Structured Concurrency Java 25 ยัง preview — ต้อง --enable-previewตอน compile/runInstance Main + Compact Source Java 25 stable (JEP 512) Module Import Declarations Java 25 stable (JEP 511) String Templates — withdrawn (Java 23) กำลัง redesign
1. Text Block (Java 15+) — string หลายบรรทัด
ก่อน Java 15:
java
String json = "{\n" +
" \"name\": \"Anna\",\n" +
" \"age\": 25\n" +
"}";ปวดตา — \n กับ " เต็มไปหมด
Java 15+ มี text block — """:
java
String json = """
{
"name": "Anna",
"age": 25
}
""";- ใช้
"""คร่อม - indent อัตโนมัติ — Java เอา indent ที่น้อยที่สุดเป็น baseline (ลบ leading whitespace ที่ common)
- ไม่ต้อง escape
"ภายใน
Text block special escapes
java
// \ ท้ายบรรทัด — กัน newline (ต่อบรรทัดยาว)
String url = """
https://example.com/api/v1\
/users?active=true""";
// = "https://example.com/api/v1/users?active=true"
// \s — บังคับให้มี space (กัน trailing space ถูกตัด)
String aligned = """
col1\s
col2\s
""";String + formatted (Java 15+)
.formatted(...) = instance method ของ String — เหมือน String.format(...) แต่ chain กับ text block ได้:
java
String name = "Anna"; int age = 25;
String json = """
{
"name": "%s",
"age": %d
}
""".formatted(name, age);
// vs เก่า:
String json2 = String.format("""
{"name":"%s","age":%d}""", name, age);String methods ที่เพิ่มมาในยุคใหม่
| Method | ทำอะไร | เพิ่มเมื่อ |
|---|---|---|
strip() / stripLeading() / stripTrailing() | trim แบบ Unicode-aware (ดีกว่า trim()) | Java 11 |
isBlank() | true ถ้าว่างหรือมีแต่ whitespace | Java 11 |
lines() | Stream แต่ละบรรทัด | Java 11 |
repeat(n) | ทำซ้ำ n รอบ | Java 11 |
indent(n) | เพิ่ม/ลด indent ทุกบรรทัด | Java 12 |
formatted(args) | format แบบ instance | Java 15 |
chars() / codePoints() | IntStream ของตัวอักษร | Java 8/9 |
transform(fn) | function pipeline | Java 12 |
java
" hello ".strip(); // "hello"
" ".isBlank(); // true
"line1\nline2\nline3".lines().toList(); // [line1, line2, line3]
"=".repeat(20); // "===================="
"x\ny".indent(4); // " x\n y\n"
// transform — chain function เข้ากับ String
String slug = "Hello World!"
.transform(String::toLowerCase)
.transform(s -> s.replaceAll("\\W+", "-")); // \W+ = ตัวอักษรที่ไม่ใช่ a-z/0-9 ติดกันหนึ่งช่วง (regex — ดูบทที่ 13)
// slug = "hello-world-"2. var (Java 10+)
java
var name = "Anna"; // String
var age = 25; // int
var list = new ArrayList<String>(); // ArrayList<String>
var map = new HashMap<String, List<Integer>>(); // ยาวลดลงเยอะvar = compiler เดา type จากค่าด้านขวา
ใช้เมื่อ: type ด้านขวาชัดเจน → อ่านง่ายขึ้น ไม่ใช้เมื่อ: type ไม่ชัด → ทำให้อ่านยาก
java
var x = getResult(); // type อะไร? ไม่ชัด ❌3. Record (Java 16+) — class ที่เป็น "data carrier"
3.1 ปัญหา: class แค่เก็บข้อมูล ก็ต้องเขียนเยอะ
java
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}
}40 บรรทัดสำหรับ class ที่แค่เก็บ 2 ตัวเลข
3.2 record แก้ปัญหา
java
public record Point(int x, int y) {}1 บรรทัด! — Java สร้างให้ทุกอย่าง:
- final field
x,y - constructor
Point(int x, int y) - accessor method
x(),y()(ไม่ใช่getX()— ใหม่! · Java เก่าใช้getX()เป็น convention — record ใช้ชื่อ field ตรง ๆ คือx()) equals,hashCodeตาม fieldtoStringแสดง field
java
Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
System.out.println(p1.x()); // 3
System.out.println(p1); // Point[x=3, y=4]
System.out.println(p1.equals(p2)); // true3.3 เพิ่ม method ได้
java
public record Point(int x, int y) {
public double distanceFromOrigin() {
return Math.sqrt(x * x + y * y);
}
}3.4 Validation ใน constructor
java
public record Box(int width, int height) {
public Box { // compact constructor (คอนสตรักเตอร์แบบย่อของ record)
if (width < 0 || height < 0) {
throw new IllegalArgumentException("width/height must be non-negative");
}
}
}public Box { ... } (ไม่มี parameter list) = "compact constructor" — run ก่อน field assign อัตโนมัติ
💡 เปรียบเทียบ syntax:
- compact:
public Box { ... }— Java auto-assignthis.width = width; this.height = height;ให้หลัง body- canonical (เต็ม):
public Box(int width, int height) { ... this.width = width; ... }— ต้องเขียน assign เองส่วนใหญ่ใช้ compact form พอ — สั้นกว่า + ตั้งใจสำหรับ validation/normalization
3.5 ใช้ record เมื่อไหร่
✅ ใช้ เมื่อ class แค่เก็บข้อมูล (DTO, value object)
record User(String name, int age) {}record Coordinate(double lat, double lng) {}
❌ อย่าใช้ เมื่อ:
- class ต้องเปลี่ยน state (record immutable)
- ต้องการ inherit (record extend ไม่ได้)
- มี logic ซับซ้อนใน class
3.6 record + Optional + Stream — combo ยอดฮิต
java
record Order(String id, String customerId, double amount, boolean paid) {}
List<Order> orders = List.of(
new Order("1", "A", 100, true),
new Order("2", "A", 200, false),
new Order("3", "B", 150, true)
);
double totalPaid = orders.stream()
.filter(Order::paid)
.mapToDouble(Order::amount)
.sum();4. Sealed Class/Interface (Java 17+)
4.1 ปัญหา: ปกติ class/interface เปิดให้ใครก็ extend ได้
java
abstract class Shape { ... }
class Circle extends Shape { ... }
class Square extends Shape { ... }
// ใครก็เพิ่มได้
class Hexagon extends Shape { ... }บางที่เราอยากบอก "Shape มีแค่ Circle/Square — ห้ามเพิ่มอย่างอื่น"
4.2 sealed บังคับ subtype
java
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
// ❌ error: class is not allowed to extend sealed class
class Hexagon implements Shape {}permits Circle, Square = "อนุญาตให้ implement แค่ 2 ตัวนี้"
4.3 ทำไม sealed — pattern matching exhaustive
java
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square sq -> sq.side() * sq.side();
// ไม่ต้องมี default! Java รู้ว่ามีแค่ 2 ตัว
};
}ถ้าเพิ่ม Triangle ใน sealed → switch จะ compile error ทันที — บังคับให้แก้ทุก switch ป้องกัน bug แบบ "ลืม update ที่ใดที่หนึ่ง"
4.4 Modifiers ของ permitted classes
class ที่ permits ต้องเป็น:
final— ห้าม extend ต่อsealed— sealed ต่อnon-sealed— เปิดให้ใครก็ extend ต่อ
java
sealed interface Shape permits Circle, Polygon {}
final class Circle implements Shape { ... } // จบที่นี่
non-sealed class Polygon implements Shape { ... } // เปิดต่อ
sealed class RegularPolygon extends Polygon permits Square, Hexagon { ... }5. Pattern Matching
5.1 Pattern matching for instanceof (Java 16+)
แบบเก่า:
java
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}แบบใหม่:
java
if (obj instanceof String s) {
System.out.println(s.length());
}instanceof String s = "เช็คว่าเป็น String + bind ให้ตัวแปร s"
5.2 Pattern matching for switch (Java 21 stable)
java
String describe(Object obj) {
return switch (obj) {
case Integer i -> "int: " + i;
case String s -> "string: " + s;
case List<?> l when l.isEmpty() -> "empty list";
case List<?> l -> "list of " + l.size();
case null -> "null";
default -> "other";
};
}case Integer i— เช็ค type + bindcase ... when condition— guard เพิ่มเงื่อนไขcase null— รับ null ได้ (เคยไม่ได้)- ไม่ต้อง break — Java 14+ switch expression
5.3 Record pattern (Java 21+)
deconstruct record ใน case:
java
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Rectangle(double w, double h) -> w * h;
};
}ไม่ต้องดึง field ด้วย accessor — มันแกะให้เลย
5.4 Nested record pattern + var
java
record Point(int x, int y) {}
record Line(Point start, Point end) {}
double length(Line ln) {
return switch (ln) {
case Line(Point(var x1, var y1), Point(var x2, var y2)) ->
Math.hypot(x2 - x1, y2 - y1);
};
}var ใน pattern → ใช้ type inference, ไม่ต้องเขียน int ซ้ำ
5.5 Unnamed variable + pattern _ (Java 22+ stable)
ถ้าไม่ต้องการชื่อตัวแปร → ใช้ _:
java
// ใน lambda — ไม่ใช้ argument
map.forEach((_, v) -> System.out.println(v)); // ignore key
// ใน catch — ไม่ใช้ exception object
try {
Integer.parseInt(s);
} catch (NumberFormatException _) {
return 0;
}
// ใน try-with-resources (Java 22+ — บางที resource ไม่ต้องอ้างชื่อ)
// ตัวอย่างสมมติกับ lock library ภายนอกที่ acquire() คืน AutoCloseable (เรื่อง lock จริงอยู่บทที่ 12)
// เช่น: try (var _ = Files.newBufferedReader(path)) { ... }
try (var _ = lock.acquire()) {
work();
}
// ใน record pattern — ไม่สนค่านี้
record Point(int x, int y) {}
if (p instanceof Point(int x, _)) { // อ่านแค่ x, ทิ้ง y
use(x);
}
// switch — combine กับ pattern (ใช้ Shape จาก section 5.3 — เพิ่ม Triangle เพื่อสาธิต _)
// sealed interface Shape permits Circle, Rectangle, Triangle {}
// record Triangle(double base, double height) implements Shape {}
switch (shape) {
case Circle(double r) -> area(r);
case Rectangle(_, double h) -> h; // สน height อย่างเดียว
case Triangle _ -> 0; // ไม่ดึง field ใด ๆ
}💡 ใช้
_แทนunused/ignored— ทำให้ compiler/linter ไม่เตือน "unused variable"
6. Sealed + Record + Pattern Matching = ADT
3 อย่างรวมกันคือ ADT (Algebraic Data Type) = วิธีออกแบบ type ที่กำหนดชัดเจนว่ามีรูปแบบข้อมูลกี่แบบ และบังคับให้โค้ดรับมือให้ครบทุกแบบ — compiler เลยเช็คให้ได้ว่า handle ครบ
💡 ADT คืออะไร: sealed class ช่วยให้เราบอกว่า type หนึ่งมีแค่ N รูปแบบเท่านั้น ไม่มีรูปแบบที่ N+1 — compiler จะตรวจให้ว่า switch ครอบคลุมทุกกรณีหรือยัง แนวคิดนี้มีในหลายภาษาระดับสูง ไม่จำเป็นต้องรู้ภาษาอื่นก็ใช้ใน Java ได้เลย
ตัวอย่าง — model "result" ที่อาจสำเร็จหรือ error:
java
sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error) implements Result<T> {}
// ใช้
Result<Integer> r = compute();
String msg = switch (r) {
case Success(Integer v) -> "Got " + v;
case Failure(String e) -> "Error: " + e;
};— ไม่ต้องมี null, ไม่ต้อง throw, type system บังคับให้ handle ทั้ง 2 case
7. Switch Expression (Java 14 stable)
ใช้ switch เป็น expression — return ค่าได้:
java
int day = 3;
String name = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
default -> "Unknown";
};หลายค่าใน case เดียว:
java
String type = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> "Weekend";
default -> "Unknown";
};Block + yield:
java
String description = switch (day) {
case 1 -> "Monday";
case 7 -> {
System.out.println("It's Sunday!");
yield "Sunday";
}
default -> "Other";
};8. Virtual Threads (Java 21+)
8.1 ปัญหาของ thread แบบเดิม
java
new Thread(() -> {
// ทำงาน
}).start();Thread แบบเดิม (platform thread) คือ "thread ของ OS" — แพง:
- 1 thread กิน ~2MB memory
- สร้าง/ทำลายช้า
- OS limit ~5,000-10,000 thread/process
ดังนั้นเขียน blocking I/O แบบ:
java
for (int i = 0; i < 1_000_000; i++) {
new Thread(() -> {
readFile(); // blocking (งานที่ค้างรอ เช่น รออ่านไฟล์/เครือข่าย)
}).start();
}ไม่ได้ — ระเบิด
8.2 Virtual Thread — thread ที่ JVM จัดการ
java
Thread.startVirtualThread(() -> {
readFile();
});Virtual thread:
- 1 thread กิน ~KB เท่านั้น
- สร้างได้เป็น ล้านตัว
- JVM map หลาย virtual thread → platform thread จริงไม่กี่ตัว
8.3 Executors
ExecutorService = ตัวจัดการ thread ให้เรา — ส่งงานเข้าไปแล้วมันดูแล thread เอง (รายละเอียดเต็มอยู่บทที่ 12 section 3)
java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> {
// ทำงาน
});
}
}8.4 ทำไม virtual thread จึงเปลี่ยนวิธีเขียนโค้ดไปอย่างมาก (game changer)
ก่อน virtual thread:
- เขียน async/reactive (การเขียนโค้ดแบบไม่บล็อก รับ-ส่งข้อมูลแบบไหล) เพื่อให้ scale เช่น:
CompletableFuture(บทที่ 12)- RxJava (library async ยอดนิยมก่อนยุค virtual thread)
- Mono/Flux (ชนิดข้อมูลใน Project Reactor ซึ่งเป็น library reactive สำหรับ Spring — ไม่ต้องรู้จักตอนนี้)
- โค้ดอ่านยาก, debug ยาก
ตอนนี้:
- เขียน blocking สไตล์เดิม
- แต่ scale ได้แล้ว
— Spring Boot 3.2+ รองรับการเปิด virtual thread ผ่าน spring.threads.virtual.enabled=true (ไม่ได้เปิดเป็น default — ต้อง opt-in)
8.5 ⚠️ Pitfalls ที่ production เจอจริง — อ่านก่อนเปิด virtual thread ใน prod
🔴 ก่อน Java 24 + บางเคสยังอยู่: synchronized + blocking I/O = pin carrier thread ทำให้ scale ไม่ขึ้นจริงตามที่หวัง
Pinning คืออะไร: virtual thread ปกติเวลา block (I/O, sleep) จะถูก "ถอด" จาก carrier thread (platform thread ของ OS ที่ virtual thread ฝากตัวอยู่) เพื่อปล่อยให้ virtual thread อื่นวิ่ง — แต่บางสถานการณ์ JVM ถอดไม่ได้ → carrier ค้าง = pinning → ถ้า pin ครบทุก carrier = ระบบหยุด
สาเหตุ pinning หลัก ๆ:
| สาเหตุ | สถานะ |
|---|---|
synchronized block + blocking call ข้างใน | Java 21-23 = pin, Java 24+ ไม่ pin แล้ว (JEP 491) |
| Native frame (JNI = Java Native Interface — การเรียก code C/C++ จาก Java) ที่ยัง block อยู่ | ยัง pin (ทุกเวอร์ชัน) — เลี่ยงไม่ได้ |
Object.wait() (legacy lock) | Java 21-23 = pin, Java 24+ ไม่ pin แล้ว (JEP 491) — แนะนำใช้ ReentrantLock.lockInterruptibly() แทน |
Class initializer (<clinit>) ที่ block | pin (rare) |
ตรวจหา pinning ในระบบ:
bash
# Java 21-23: เปิด flag ระหว่าง dev/staging
-Djdk.tracePinnedThreads=full # log stack trace ทุกครั้งที่ pin
# Java 21+: ดูผ่าน JFR event
# jcmd = เครื่องมือสั่งงาน JVM ที่กำลังรัน, <pid> = หมายเลข process (ดูได้จาก `jps`)
# JFR = Java Flight Recorder (ตัวบันทึกเหตุการณ์ใน JVM)
# JMC = JDK Mission Control (โปรแกรม GUI เปิดดู recording)
jcmd <pid> JFR.start name=pin settings=profile
# เปิด recording ใน JMC → ดู event "jdk.VirtualThreadPinned"กฎใช้งานจริง (ปี 2026):
- ถ้ายังอยู่ Java 21–23 → audit code: เปลี่ยน
synchronizedblock ที่ครอบ DB/HTTP/file I/O เป็นReentrantLock(บทที่ 12) - ถ้าใช้ Java 24+ → synchronized ปลอดภัยแล้ว (JEP 491 GA มีนาคม 2025) แต่ JNI ยัง pin
ThreadLocal(ที่เก็บข้อมูลแยกต่อ thread — ดูบทที่ 12) ยังใช้ได้ แต่ระวังเรื่อง memory — ถ้ามี virtual thread เป็นล้านตัว แต่ละตัวถือThreadLocalขนาดใหญ่ ก็รวมกันเป็น OOM ได้ง่าย ๆ (OutOfMemoryError = หน่วยความจำเต็ม) ถ้าเป็นไปได้ให้ใช้ Scoped Values แทน (stable ตั้งแต่ Java 25 — Java 21-24 เป็น preview)- JDBC connection pool ยังคอขวด — JDBC (ตัวเชื่อมต่อ Java กับ database — บทที่ 17), HikariCP (library จัดการ connection pool ยอดนิยม) มี 10-30 connections → bottleneck (จุดคอขวด = ส่วนที่ทำให้ระบบช้า) ย้ายไปที่ pool/DB ไม่ใช่ thread
9. Stream Gatherers (Java 24 stable)
Stream.gather(...) = custom intermediate operation (ขั้นตอนกลางใน stream เช่น map/filter ที่ยังไม่สิ้นสุดการประมวลผล — ดูบทที่ 9) — เหมือนเขียน operator ของตัวเองเทียบเท่า map/filter/flatMap ที่ Java ไม่มีให้
✅ อัปเดต (ปี 2026): stable ตั้งแต่ Java 24 (JEP 485) — Java 25 ใช้ได้เลย ไม่ต้องเปิด
--enable-preview
Built-in gatherer ใน java.util.stream.Gatherers
java
import java.util.stream.Gatherers;
import java.util.stream.Stream;
// 1) windowFixed — แบ่งกลุ่มขนาดเท่ากัน (ไม่ overlap)
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowFixed(2))
.toList(); // [[1,2], [3,4], [5]]
// 2) windowSliding — หน้าต่างเลื่อน (overlap)
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.toList(); // [[1,2,3], [2,3,4], [3,4,5]]
// 3) fold — เหมือน reduce: คืนค่าสุดท้ายค่าเดียว (stateful ใช้ใน gather ได้)
Stream.of(1, 2, 3, 4)
.gather(Gatherers.fold(() -> 0, Integer::sum))
.toList(); // [10]
// 4) scan — running total (return ทุกค่ากลาง ไม่ใช่แค่ค่าสุดท้าย)
Stream.of(1, 2, 3, 4)
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList(); // [1, 3, 6, 10]
// 5) mapConcurrent — map ขนานบน virtual threads (max N พร้อมกัน)
urls.stream()
.gather(Gatherers.mapConcurrent(10, url -> httpGet(url)))
.toList();Custom gatherer
เขียนเองได้ — define Gatherer<T, A, R> (T = ชนิดข้อมูลขาเข้า, A = state กลาง, R = ชนิดผลลัพธ์) — ยุ่งกว่าหน่อย ลึกเกินบทนี้ แต่รู้ว่าเปิดทางทำ operator ของเราเองได้ (ค้นเพิ่มเติมด้วย keyword "java Gatherer custom")
10. Instance Main Methods + Compact Source Files (Java 25 stable, JEP 512)
ปัญหามือใหม่ตั้งแต่บทที่ 1 — เขียน Hello World ต้องมี class wrap + public static void main(String[] args) ทั้งที่ยังไม่เคยเรียน OOP เลย:
java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello");
}
}Java 25 (stable) อนุญาตให้เขียนแบบนี้แทน:
java
// ไฟล์ Hello.java
void main() {
System.out.println("Hello");
}— ไม่ต้องมี class, ไม่ต้อง static, ไม่ต้องรับ args, ไม่ต้อง public
💡 ปกติ main ต้องเขียน
public static void main(String[] args)— Java 25 อนุญาตให้เขียนสั้นลงเป็นvoid main()ได้สำหรับโปรแกรมเล็ก ๆ ทดลอง ถ้ายังไม่แน่ใจให้ใช้แบบเดิมก่อนได้เลย
run แบบ direct source บน JDK 25: java Hello.java
💡 ถ้าใช้ JDK 21-24 ที่ feature ยังเป็น preview ต้องเพิ่ม flag โดยใส่เลขเวอร์ชัน JDK ของตัวเองใน
--sourceเช่น JDK 21 ใช้:java --enable-preview --source 21 Hello.java(--enable-preview= เปิดฟีเจอร์ทดลอง,--source 21= บอกว่าใช้ syntax Java 21 — ตัวเลขต้องตรงกับ JDK ที่ติดตั้ง ห้ามใส่เลขสูงกว่า JDK จริง) พิมพ์คำสั่งนี้ใน Terminal (หรือ Command Prompt บน Windows) ที่โฟลเดอร์เดียวกับไฟล์ Hello.java
ทำไมสำคัญ
- มือใหม่เริ่มเขียน Java ได้เหมือน Python/JS — ไม่ต้องเข้าใจ OOP ก่อน
- pedagogy ดีขึ้นมาก — สอนพื้นฐานก่อน, อธิบาย class ทีหลังเมื่อจำเป็น
- stable ใน Java 25 — ใช้ได้ใน production แล้ว (ไม่ต้อง flag)
📌 ยังต้องเรียนแบบเก่าด้วย — code base ที่มีอยู่ทั้งโลก ใช้
public class + public static void mainทั้งหมด
11. Module Import Declarations (Java 25 stable, JEP 511)
แทนที่ต้อง import class ทีละตัว — import ทั้ง module เลย:
java
// แบบเดิม
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// แบบใหม่ (stable ใน Java 25)
import module java.base; // ทุก class ที่ java.base export (เปิดให้ code ภายนอก module เข้าถึงได้)java.base export ~120 package พื้นฐาน (java.util, java.io, java.nio.file, java.time, java.lang.*, ...) — แทน import ได้เกือบทั้งหมดในไฟล์เล็ก
เหมาะกับ:
- script / one-off
- ตัวอย่างใน tutorial
- ทำงานคู่กับ instance main + compact source
⚠️ ใน production code ใหญ่ ๆ อย่าใช้ — ไม่ชัดว่า code พึ่ง symbol ไหน
12. Scoped Values (Java 25 stable, JEP 506)
ปัญหาของ ThreadLocal:
- mutable (เปลี่ยนค่าได้) — ใครก็เปลี่ยนได้
- ลืม
.remove()→ memory leak (โดยเฉพาะกับ virtual threads ที่มีล้านตัว) - ส่งต่อค่าระหว่าง task ลำบาก
Scoped Values = "ThreadLocal ที่ immutable + bounded scope + virtual-thread friendly":
java
import java.lang.ScopedValue;
static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
// bind ค่า + run code ใน scope นั้น
ScopedValue.where(USER_ID, "user-123")
.run(() -> handleRequest());
// ใน handleRequest หรือ method ที่เรียกต่อ
void handleRequest() {
String uid = USER_ID.get(); // "user-123"
saveOrder(...); // method นี้เห็น USER_ID เหมือนกัน
}
void saveOrder(...) {
log("by user " + USER_ID.get()); // ส่งต่อโดยไม่ต้อง pass parameter
}ข้อดี vs ThreadLocal
| ThreadLocal | ScopedValue | |
|---|---|---|
| เปลี่ยนค่าได้ (Mutable)? | ✅ | ❌ (set ทีเดียวต่อ scope) |
| Auto cleanup | ❌ ต้อง .remove() | ✅ จบ scope = หมด |
| Inheritance to child task | InheritableThreadLocal (ThreadLocal แบบพิเศษที่ส่งค่าต่อให้ thread ลูกได้ แต่ใช้งานซับซ้อน) | ส่งต่ออัตโนมัติด้วย StructuredTaskScope |
| Virtual thread friendly | ❌ leak ง่าย | ✅ ออกแบบมา |
| Memory model | ต้องระวัง | thread-safe by design |
💡 ใน Spring Boot 3.x — ใช้ Scoped Values แทน
RequestContextHolderในงาน virtual thread ได้
13. Foreign Function & Memory API (Java 22+)
แทน JNI (Java Native Interface) เก่า — เรียก native library (C/C++) จาก Java
📖 ตัวละครหลักใน FFM API:
Linker— ตัวเชื่อมต่อกับ native library ของ OSSymbolLookup— ค้นหา function ใน library (เช่นstrlenใน C standard library)MethodHandle— ตัวแทนของ function ที่จะเรียก (เหมือน function pointer ใน C)FunctionDescriptor— ประกาศ signature ของ function (รับอะไร/คืนอะไร)ValueLayout— บอก JVM ว่า C type นั้นมีขนาด/format อย่างไรArena— ตัวจัดการ memory นอก Java heap (auto-close เมื่อจบ try-with-resources)MemorySegment— พื้นที่ memory ที่ allocate ไว้ (คล้าย pointer ใน C แต่ปลอดภัยกว่า)
java
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle; // MethodHandle อยู่ package invoke ไม่ใช่ foreign — ต้อง import แยก
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").get(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment str = arena.allocateFrom("Hello"); // Java 22+ API (allocateUtf8String ถูกลบใน Java 23)
// ⚠️ MethodHandle.invoke() โยน Throwable (ไม่ใช่ Exception) —
// enclosing method ต้องประกาศ throws Throwable หรือ wrap ใน try/catch ดังนี้:
long len;
try {
len = (long) strlen.invoke(str);
} catch (Throwable t) {
throw new RuntimeException("FFM invoke failed", t);
}
System.out.println(len); // 5
}(ลึก — รู้ว่ามีก็พอ ยังไม่ใช้ใน work ส่วนใหญ่)
14. ตัวอย่างใหญ่ — ระบบ payment
มาประกอบฟีเจอร์ modern Java ทั้งหมดในบทเข้าด้วยกันเป็นระบบชำระเงินจริง — ใช้ sealed interface (จำกัดชนิดที่เป็นไปได้), record (ข้อมูลกระชับ) และ pattern matching ใน switch เห็นพลังของการออกแบบ type ที่ปลอดภัยและอ่านง่าย:
java
sealed interface PaymentMethod permits Cash, CreditCard, BankTransfer {}
record Cash(double amount) implements PaymentMethod {}
record CreditCard(String number, String name) implements PaymentMethod {} // ⚠️ ในระบบจริง ข้อมูล sensitive เช่น เลขบัตร ต้องใช้ record ที่ override toString() เพื่อ mask ค่า หรือไม่เก็บเป็น plaintext — ตัวอย่างนี้ simplified สำหรับสาธิต
record BankTransfer(String bankCode, String accountNumber) implements PaymentMethod {}
sealed interface PaymentResult permits Approved, Declined {}
record Approved(String transactionId) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}
public class PaymentProcessor {
public PaymentResult process(PaymentMethod method, double amount) {
return switch (method) {
case Cash(double cashAmount) when cashAmount >= amount ->
new Approved("CASH-" + System.currentTimeMillis());
case Cash c ->
new Declined("Insufficient cash: " + c.amount());
case CreditCard(String num, String name) ->
processCard(num, name, amount);
case BankTransfer(String bank, String acc) ->
processBank(bank, acc, amount);
};
}
private PaymentResult processCard(String number, String name, double amount) {
// ...
return new Approved("CARD-" + name);
}
private PaymentResult processBank(String bank, String acc, double amount) {
// ...
return new Approved("BANK-" + bank);
}
}โค้ดด้านบนใช้: record, sealed interface, switch expression, record pattern, guard — combo ของ Modern Java
15. Checkpoint
🛠️ Checkpoint 10.1 — record
แปลง class Point, Rectangle, Circle จากบทที่ 5 (OOP) เป็น record
📖 เฉลย
java
record Point(int x, int y) {}
record Rectangle(double width, double height) {
double area() { return width * height; }
}
record Circle(double radius) {
double area() { return Math.PI * radius * radius; }
}🛠️ Checkpoint 10.2 — sealed + switch
สร้าง sealed interface Animal permits Dog, Cat, Bird (เป็น record ทั้งหมด) สร้าง method String sound(Animal a) — return เสียงของแต่ละชนิด ใช้ switch pattern
📖 เฉลย
java
sealed interface Animal permits Dog, Cat, Bird {}
record Dog(String name) implements Animal {}
record Cat(String name) implements Animal {}
record Bird(String name) implements Animal {}
public static String sound(Animal a) {
return switch (a) {
case Dog d -> d.name() + ": Woof!";
case Cat c -> c.name() + ": Meow!";
case Bird b -> b.name() + ": Tweet!";
};
}
// ใช้
sound(new Dog("Buddy")); // "Buddy: Woof!"🛠️ Checkpoint 10.3 — Optional + switch pattern
มี method findUser(id) -> Optional<User> เขียน method getEmailOrDefault(id, defaultEmail) ใช้ switch pattern:
- ถ้า empty → return defaultEmail
- ถ้ามี user → return user.email
📖 เฉลย
java
public static String getEmailOrDefault(String id, String defaultEmail) {
Optional<User> opt = findUser(id);
return opt.map(User::email).orElse(defaultEmail);
}
// หรือใช้ switch pattern (รูปแบบใหม่ — สำหรับฝึก)
public static String getEmailOrDefault2(String id, String defaultEmail) {
return switch (findUser(id).orElse(null)) {
case User u -> u.email();
case null -> defaultEmail;
};
}(แบบแรกอ่านง่ายกว่า — Optional method ทำเองได้แล้ว)
16. สรุปบท
✅ Text block """...""" — string หลายบรรทัด ✅ var — type inference ใน local variable ✅ record — class แบบเก็บข้อมูล, immutable, auto-gen equals/hashCode/toString ✅ sealed — จำกัด subtype, ใช้กับ pattern matching ทำ exhaustive check ✅ Pattern matching ใน instanceof + switch — รวม record pattern ✅ Switch expression — return ค่า, ไม่ต้อง break ✅ Virtual threads — scale ได้ล้าน thread, เขียน blocking สบาย ๆ ✅ Combo sealed + record + pattern match = ADT — pattern ที่ type-safe สุด ๆ
🎉 จบโซนพื้นฐาน Java (บท 1–10)
ถ้าคุณอ่านมาถึงตรงนี้ ทำ checkpoint หมด — ขอแสดงความยินดี! คุณมีพื้นฐาน Java แน่นพอที่จะ:
- อ่านโค้ด Java ที่ใครเขียนได้
- เขียน Java program ที่ทำงานได้จริง
- เริ่มเรียน framework (Spring Boot)
- เริ่มเรียน design pattern, concurrency, JVM ลึก
📌 บท 11–19 คือโซนอ้างอิง — เปิดอ่านตอนต้องใช้ ไม่ต้องอ่านเรียงทีเดียวหมด:
- บทที่ 11 — Date/Time, IO, Files
- บทที่ 12 — Concurrency — thread, executor, lock, atomic, Scoped Values เต็มรูปแบบ
- บท 13+ — Generics, Regex, JDBC, Testing, ฯลฯ
ขั้นต่อไป
- เริ่ม Spring Boot — ไปบทแรกของ Spring Boot
- ฝึก project เล็ก ๆ: เขียน CLI tool, todo list, calculator, file processor
- อ่านโค้ดของคนอื่น — clone repo บน GitHub แล้วอ่าน
- เรียน design pattern — Strategy, Factory, Observer ฯลฯ
- เรียน concurrency — thread, executor, lock, atomic → บทที่ 12
- เรียน JVM — GC, JIT, profiling
อ้างอิงเพิ่มเติม: