Skip to content

บทที่ 10 — Modern Java (Records, Sealed, Pattern Matching, Virtual Threads, Java 25 LTS)

← บทที่ 9 | สารบัญ | ขึ้น Spring Boot →

บทสุดท้าย — 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 ชัด)

📖 ศัพท์ในตารางข้างล่าง — รู้ก่อน:

  • 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):

FeatureStable ตั้งแต่สถานะ
varJava 10stable
Text BlockJava 15stable
RecordJava 16stable
Pattern matching instanceofJava 16stable
Sealed class/interfaceJava 17stable
Switch expressionJava 14stable
Pattern matching switchJava 21stable
Record patternJava 21stable
Virtual ThreadsJava 21stable
Sequenced CollectionsJava 21stable (บทที่ 7)
Stream GatherersJava 24stable
Scoped ValuesJava 25stable
Structured ConcurrencyJava 25ยัง preview — ต้อง --enable-preview ตอน compile/run
Instance Main + Compact SourceJava 25stable (JEP 512)
Module Import DeclarationsJava 25stable (JEP 511)
String Templateswithdrawn (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 ถ้าว่างหรือมีแต่ whitespaceJava 11
lines()Stream แต่ละบรรทัดJava 11
repeat(n)ทำซ้ำ n รอบJava 11
indent(n)เพิ่ม/ลด indent ทุกบรรทัดJava 12
formatted(args)format แบบ instanceJava 15
chars() / codePoints()IntStream ของตัวอักษรJava 8/9
transform(fn)function pipelineJava 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 ตาม field
  • toString แสดง 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));   // true

3.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-assign this.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 + bind
  • case ... 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>) ที่ blockpin (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):

  1. ถ้ายังอยู่ Java 21–23 → audit code: เปลี่ยน synchronized block ที่ครอบ DB/HTTP/file I/O เป็น ReentrantLock (บทที่ 12)
  2. ถ้าใช้ Java 24+ → synchronized ปลอดภัยแล้ว (JEP 491 GA มีนาคม 2025) แต่ JNI ยัง pin
  3. ThreadLocal (ที่เก็บข้อมูลแยกต่อ thread — ดูบทที่ 12) ยังใช้ได้ แต่ระวังเรื่อง memory — ถ้ามี virtual thread เป็นล้านตัว แต่ละตัวถือ ThreadLocal ขนาดใหญ่ ก็รวมกันเป็น OOM ได้ง่าย ๆ (OutOfMemoryError = หน่วยความจำเต็ม) ถ้าเป็นไปได้ให้ใช้ Scoped Values แทน (stable ตั้งแต่ Java 25 — Java 21-24 เป็น preview)
  4. 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

ThreadLocalScopedValue
เปลี่ยนค่าได้ (Mutable)?❌ (set ทีเดียวต่อ scope)
Auto cleanup❌ ต้อง .remove()✅ จบ scope = หมด
Inheritance to child taskInheritableThreadLocal (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 ของ OS
  • SymbolLookup — ค้นหา 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 คือโซนอ้างอิง — เปิดอ่านตอนต้องใช้ ไม่ต้องอ่านเรียงทีเดียวหมด:

ขั้นต่อไป

  1. เริ่ม Spring Bootไปบทแรกของ Spring Boot
  2. ฝึก project เล็ก ๆ: เขียน CLI tool, todo list, calculator, file processor
  3. อ่านโค้ดของคนอื่น — clone repo บน GitHub แล้วอ่าน
  4. เรียน design pattern — Strategy, Factory, Observer ฯลฯ
  5. เรียน concurrency — thread, executor, lock, atomicบทที่ 12
  6. เรียน JVMGC, JIT, profiling

อ้างอิงเพิ่มเติม:

→ ไปเรียน Spring Boot