Skip to content

บทที่ 4 — Methods และ Scope

← บทที่ 3 | สารบัญ | บทที่ 5: OOP

จนถึงตอนนี้ทั้งโปรแกรมเราอยู่ใน main method เดียว — โปรแกรมจริงไม่ทำแบบนั้น

Method = หน่วยย่อยของโปรแกรม ที่ "ตั้งชื่อ" + "นำกลับมาใช้ใหม่ได้"

หลังจบบทคุณจะ:

  • สร้าง method + เรียกใช้ + return ค่า
  • เข้าใจ parameter (pass by value)
  • ใช้ method overloading
  • เข้าใจ scope ของตัวแปร
  • รู้จัก static vs non-static (พื้นฐาน — บทที่ 5 จะลึกอีก)

1. ทำไมต้องมี method

ลองดูโค้ดที่ "ทำซ้ำ":

java
public class Main {
    public static void main(String[] args) {
        // คำนวณพื้นที่สี่เหลี่ยม 3 รูป
        int width1 = 5, height1 = 10;
        int area1 = width1 * height1;
        System.out.println("Area 1: " + area1);
        
        int width2 = 3, height2 = 7;
        int area2 = width2 * height2;
        System.out.println("Area 2: " + area2);
        
        int width3 = 8, height3 = 4;
        int area3 = width3 * height3;
        System.out.println("Area 3: " + area3);
    }
}

ปัญหา:

  • ซ้ำ — logic เดียวกันเขียน 3 รอบ
  • แก้ยาก — ถ้าเปลี่ยน formula ต้องแก้ 3 ที่
  • อ่านยาก — ต้องอ่านทุกบรรทัดถึงจะรู้ว่าทำอะไร

แก้ด้วย method:

java
public class Main {
    public static int calculateArea(int width, int height) {
        return width * height;
    }
    
    public static void main(String[] args) {
        System.out.println("Area 1: " + calculateArea(5, 10));
        System.out.println("Area 2: " + calculateArea(3, 7));
        System.out.println("Area 3: " + calculateArea(8, 4));
    }
}

ดีกว่ายังไง:

  • ไม่ซ้ำ — เขียน logic ครั้งเดียว
  • อ่านง่าย — ชื่อ calculateArea(5, 10) บอกชัดว่าทำอะไร
  • แก้ทีเดียวจบ — เปลี่ยน formula ที่เดียว
  • test ได้แยก — test calculateArea(5, 10) == 50 ตรง ๆ

2. Syntax ของ method

💡 static ในบทนี้คืออะไร (คำอธิบายชั่วคราว): static = method ของ ไฟล์/class ไม่ใช่ของ object

ตอนนี้ใส่ไปก่อนได้เลย เพราะ main เป็น static — method ที่เรียกจาก main ต้องเป็น static ด้วย ถึงจะเรียกกันได้ทันทีโดยไม่ต้องสร้าง object ก่อน

ส่วน "method ของ class" ต่างจาก "method ของ object" ยังไง — และทำไม method ใน class ที่เรา new เองไม่ต้องใส่ static — เรื่องนี้จะเข้าใจเต็ม ๆ ในบทที่ 5 (OOP) ตอนนี้ขอแค่จำว่า "ใส่ static ไป method จะใช้ได้จาก main"

java
public static int calculateArea(int width, int height) {
    return width * height;
}

แยกเป็นชิ้น:

ส่วนความหมาย
publicaccess modifier (ตัวกำหนดการเข้าถึง) — ใครสามารถเรียก method นี้ได้
staticmethod ของ class (ไม่ใช่ของ object) — เดี๋ยวจะเข้าใจในบทที่ 5
intreturn type — return ค่าประเภทอะไร
calculateAreaชื่อ method — convention: camelCase, ขึ้นต้นด้วยกริยา
(int width, int height)parameter list — input ที่รับเข้ามา
{...}body — โค้ดที่จะถูกรัน (ทำงาน)
return width * height;ส่งค่ากลับ

Return type

  • void — ไม่ return ค่า
  • int / double / String / ... — return ค่าประเภทนั้น
  • int[] / List<...> — return collection (List คือ collection สำหรับเก็บของหลายตัว — เรียนละเอียดบทที่ 7 ตอนนี้แค่รู้ว่า return ได้)
  • custom type — return object (เช่น User, Order)

method ที่ไม่ return ค่า

java
public static void printGreeting(String name) {
    System.out.println("Hello, " + name + "!");
    // ไม่มี return
}

void method ไม่ต้องมี return แต่ใส่ return; (ว่าง ๆ) ก็ได้เพื่อออกก่อน:

java
public static void printIfPositive(int n) {
    if (n <= 0) {
        return;   // ออกทันที — early return
    }
    System.out.println("Positive: " + n);
}

3. เรียก method

java
// เรียก + เก็บผลในตัวแปร
int area = calculateArea(5, 10);

// เรียก + ใช้ทันที
System.out.println(calculateArea(5, 10));

// เรียกเฉย ๆ ไม่เก็บผล (void method)
printGreeting("Anna");

// nested call
int total = calculateArea(5, 10) + calculateArea(3, 7);

3.1 Argument vs Parameter — คำที่งงบ่อย

  • Parameter = ชื่อตัวแปรที่ method ใช้ (ในนิยาม method)
  • Argument = ค่าจริงที่ส่งเข้าไปตอนเรียก
java
public static int add(int a, int b) {   // a, b = parameters
    return a + b;
}

int result = add(5, 10);   // 5, 10 = arguments

(ในชีวิตจริงคนเรียก "argument" ทั้งคู่ — เรียนความต่างเผื่อเจอในเอกสารหรือคำอธิบาย compiler เช่น "wrong number of arguments" ใช้สนทนาทั่วไปแบบ argument ก็พอ)


4. Pass by Value — Java ส่งค่าเข้า method ยังไง

Java เป็น "pass by value" (ส่งค่าโดยการคัดลอก) เสมอ — เวลาส่ง argument, Java จะ copy ค่า ส่งเข้าไป

🔁 ทบทวน primitive vs reference (จากบทที่ 2):

  • primitive (int, double, boolean, ...) — ตัวแปรเก็บ ค่าจริง ตรง ๆ
  • reference (String, int[], List, object ทั่วไป) — ตัวแปรเก็บ ป้ายชี้ ไปยัง object ที่อยู่ใน heap (พื้นที่หน่วยความจำที่ Java ใช้เก็บ object)
primitive:  [int x = 5]  →  ตัวแปรมี "5" อยู่ในตัว
reference:  [int[] a]    →  ──ชี้──▶  [1, 2, 3]  (ของจริงอยู่ที่อื่น)

ความต่างนี้เป็นกุญแจที่ทำให้ section 4.2 ดู "งง" — copy ป้ายชี้ ≠ copy ของจริง

4.1 Primitive

java
public static void doubleIt(int n) {
    n = n * 2;
    System.out.println("Inside: " + n);
}

public static void main(String[] args) {
    int x = 5;
    doubleIt(x);
    System.out.println("Outside: " + x);
}

ผลลัพธ์:

text
Inside: 10
Outside: 5     ← x ยังเป็น 5!

ทำไม: ตอนเรียก doubleIt(x) Java copy ค่า 5 ของ x ไปใส่ใน nn เป็นตัวแปรใหม่ คนละตัวกับ x

4.2 Reference type — งงตรงนี้!

java
public static void modify(int[] arr) {
    arr[0] = 999;
}

public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    modify(numbers);
    System.out.println(numbers[0]);   // 999! (เปลี่ยนจริง)
}

อ้าว — แล้วทำไม array เปลี่ยนได้ ทั้ง ๆ ที่ Java เป็น pass by value?

คำตอบ: Java copy "reference" (ป้ายชื่อ) — ป้ายชื่อใหม่ชี้ที่ array เดียวกัน → แก้ array ผ่านป้ายไหนก็ได้

ภาพ:

text
Before call:
  numbers ──→ [1, 2, 3]

During call:
  numbers ──→ [1, 2, 3] ←── arr  (ทั้งสองชี้ที่เดียวกัน)

arr[0] = 999:
  numbers ──→ [999, 2, 3] ←── arr

4.3 แต่ถ้าทำแบบนี้ล่ะ

java
public static void replace(int[] arr) {
    arr = new int[]{99, 99, 99};   // assign reference ใหม่
}

public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    replace(numbers);
    System.out.println(numbers[0]);   // 1 (ไม่เปลี่ยน!)
}

ทำไม: arr = new int[]{...} แค่ "เปลี่ยนป้ายชื่อ arr ให้ชี้ที่ใหม่" — ป้าย numbers ยังชี้ที่เดิม

ภาพ:

text
Before:    numbers ──→ [1, 2, 3] ←── arr

arr = new int[]{99, ...}:
           numbers ──→ [1, 2, 3]
                       [99, 99, 99] ←── arr  (อันใหม่)

After return:
           numbers ──→ [1, 2, 3]
                       [99, 99, 99]  ← ไม่มีใครชี้ → GC (Garbage Collector — ตัวเก็บกวาด memory อัตโนมัติของ Java) จะเก็บคืนไป

สรุป: Java ส่งค่าโดยการคัดลอก (pass by value) — ถ้าส่ง reference เข้าไป สามารถแก้ข้อมูลของ object ได้ แต่ถ้าให้ตัวแปร (reference) ชี้ไปที่ object ใหม่ (reassign) ค่าข้างนอกจะไม่เปลี่ยน


5. Method Overloading — ชื่อเดียว parameter ต่างกัน

Java อนุญาตให้มี method ชื่อเดียวกัน ตราบใดที่ parameter ต่างกัน:

java
public static int add(int a, int b) {
    return a + b;
}

public static double add(double a, double b) {
    return a + b;
}

public static int add(int a, int b, int c) {
    return a + b + c;
}

public static String add(String a, String b) {
    return a + b;
}

ทั้ง 4 ตัวอยู่ใน class เดียวกันได้ — Java เลือกตัวที่เหมาะกับ argument ที่ส่ง:

java
add(5, 10);        // เรียกตัวแรก (int, int)
add(2.5, 3.5);     // เรียกตัวที่สอง (double, double)
add(1, 2, 3);      // ตัวที่สาม
add("Hello", "World");  // ตัวสี่

⚠️ Overload ตาม return type ไม่ได้:

java
public static int calculateAge() { return 1; }
public static double calculateAge() { return 1.0; }   // ❌ compile error

ต้องต่างที่ parameter list เท่านั้น


6. Variable Arguments / varargs (รับ argument จำนวนไม่แน่นอน)

ถ้าไม่รู้ว่ามี argument กี่ตัว ใช้ ...:

java
public static int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}

// เรียกแบบไหนก็ได้
sum();              // 0
sum(1);             // 1
sum(1, 2, 3);       // 6
sum(1, 2, 3, 4, 5); // 15

ภายใน method, numbers เป็น int[] ปกติ

กฎ: varargs ต้องเป็น parameter ตัวสุดท้าย + มีตัวเดียว

java
public static void log(String tag, int... values) { ... }   // ✅
public static void log(int... values, String tag) { ... }   // ❌

6.1 Varargs — สรุปสั้น

ตอนนี้รู้แค่ว่า varargs (int...) รับ argument กี่ตัวก็ได้ ก็พอ ส่วนกับดักต่อไปนี้เป็นเรื่องของ "ส่ง array เข้า varargs ยังไง" — กลับมาดูตอนเจอจริง

🚧 Varargs pitfalls (ขั้นกลาง — ข้ามรอบแรกได้)

Pitfall 1: ส่ง array primitive vs ส่งหลายตัว — ระวัง

varargs ของ printf คือ Object... — ดังนั้น array ของ primitive (int[]) ไม่ใช่ Object[] จะถูกห่อเป็น 1 argument:

java
Object[] arr = {1, 2, 3};
System.out.printf("%d %d %d%n", arr);                       // ✅ unpack เป็น 3 ตัว (Object[] เข้า varargs ตรง ๆ)

int[] iArr = {1, 2, 3};
System.out.printf("%d %d %d%n", iArr);                      // ❌ int[] ไม่ใช่ Object[] — นับเป็น 1 ตัว!
// → throw IllegalFormatConversionException (%d ได้รับ int[] ทั้งก้อน convert ไม่ได้)

// แก้: ใช้ Integer[] (เพราะ Integer[] เป็นชนิดย่อยของ Object[] ใน Java — array ของชนิดลูกใช้แทน array ของชนิดแม่ได้) หรือ unpack เอง
Integer[] boxed = {1, 2, 3};
System.out.printf("%d %d %d%n", (Object[]) boxed);          // ✅

Pitfall 2: overload กับ varargs — เลือกแบบไม่ varargs ก่อน

java
void f(int a) { ... }                                       // (1)
void f(int... arr) { ... }                                  // (2)
f(5);                                                        // เรียก (1) — exact match ก่อน

Pitfall 3: ส่ง null — NPE ตอน iterate

int... ภายในคือ int[] — ถ้าส่ง (int[]) null ตัวแปร ns จะเป็น null และ for-each จะ throw NullPointerException (NPE) ตอน run (compile ผ่าน):

java
static int sum(int... ns) {
    int s = 0;
    for (int n : ns) s += n;                                // 💥 NPE ถ้า ns == null
    return s;
}

sum((int[]) null);                                          // ❌ compile ผ่าน แต่ NPE ตอน run

// ป้องกัน:
static int safeSum(int... ns) {
    if (ns == null) return 0;
    int s = 0;
    for (int n : ns) s += n;
    return s;
}

7. Method Reference — shortcut ของ lambda

ข้ามรอบแรก — กลับมาอ่านหลังจบบทที่ 9 (Lambda & Stream)

🚧 Method Reference (ขั้นสูง — ข้ามได้ถ้ายังไม่ได้เรียน Lambda บทที่ 9)

section นี้ใช้ Stream (การประมวลผลข้อมูลแบบต่อเนื่อง) / Optional (wrapper สำหรับค่าที่อาจเป็น null) / Functional Interface (interface ที่มี method เดียว) ที่ยังไม่เคยเรียน อ่านแล้วจะงงแน่ ๆ

คำแนะนำ: ข้ามไป section 9 (Scope) ตอนนี้ — กลับมาอ่านอีกทีหลังจบบทที่ 9 จะเข้าใจทันที

ตอนจะส่ง method "เป็น" function (สำหรับ Stream/Optional/callback) — ใช้ :: ย่อ lambda ที่แค่เรียก method ตัวเดียว

รูปแบบตัวอย่างเท่ากับ lambdaเมื่อไหร่ใช้
Static methodInteger::parseInts -> Integer.parseInt(s)ใช้เมื่อต้องการเรียก static method โดยตรง
Bound instance (instance ที่กำหนดตายตัว)System.out::printlnx -> System.out.println(x)ใช้เมื่อ object ที่จะเรียก method ถูกกำหนดแล้ว
Unbound instance (instance มาจาก argument ที่ส่งเข้ามา)String::toUpperCases -> s.toUpperCase()ใช้เมื่อ method เรียกจาก argument ที่รับเข้ามา
ConstructorArrayList::new() -> new ArrayList<>()ใช้เมื่อต้องการสร้าง object ใหม่ผ่าน factory

ตัวอย่าง

📝 โค้ดในส่วนนี้ใช้ Java 16+ (.toList()) และ Java 8+ (Stream, method reference)

java
import java.util.*;
import java.util.stream.*;
import java.util.function.Supplier;

List<String> ids = List.of("1", "2", "3");

// 1. Static
List<Integer> nums = ids.stream().map(Integer::parseInt).toList();

// 2. Bound instance — System.out "fix" ไว้เป็น instance ตายตัว
ids.forEach(System.out::println);                           // System.out.println(id)

// 3. Unbound instance — instance คือ argument
List<String> upper = ids.stream().map(String::toUpperCase).toList();
// String::toUpperCase ≡ (String s) -> s.toUpperCase()

// 4. Constructor
Supplier<List<String>> factory = ArrayList::new;
List<String> list = factory.get();

// 4b. Array constructor — สำหรับ toArray
String[] arr = ids.stream().toArray(String[]::new);

💡 ใช้ method reference เมื่อ lambda แค่ "ส่งต่อ" argument — อ่านง่ายกว่า; ถ้า lambda มี logic เพิ่ม (s -> s.toUpperCase().trim()) → เขียน lambda ดีกว่า


8. final parameter — parameter ที่ reassign ไม่ได้

ข้ามรอบแรก

🚧 section นี้พูดถึงเรื่อง lambda / effectively final — ยังไม่ได้เรียน lambda (บทที่ 9) อ่านแล้วจะงงครึ่งหลัง

สรุปสั้น ๆ ที่พอจำได้ตอนนี้: ไม่ต้องเขียน final หน้า parameter ทุกตัวก็ได้ — Java จัดการให้แล้ว เดี๋ยวกลับมาดูตอนเรียน lambda

java
void process(final String name) {
    name = "other";                                         // ❌ compile error
}

ใช้น้อยใน Java สมัยใหม่ เพราะ:

  • ส่วนใหญ่ไม่ reassign parameter อยู่แล้ว (style guide ห้าม)
  • เพิ่ม noise — ทุก parameter ใส่ final รก

ที่ยังจำเป็น: parameter ที่ใช้ใน lambda / anonymous class ต้อง "effectively final" (ตัวแปรที่ไม่ได้ declare final แต่ Java ถือว่า final เพราะไม่มีการเปลี่ยนค่า) — Java สมัยใหม่ผ่อนปรน (ไม่ต้อง declare final ตรง ๆ แต่ก็เปลี่ยนค่าไม่ได้)

java
void demo(String name) {                                    // ไม่ใส่ final แต่ effectively final
    Runnable r = () -> System.out.println(name);            // ✅ OK
    // name = "other";                                      // ❌ ถ้า reassign → lambda พัง compile
}

💡 Rule: ไม่ต้องเขียน final หน้า parameter ทุกตัว; Java treat parameter ที่ไม่ถูก reassign ว่า "effectively final" อยู่แล้ว


9. Scope — ตัวแปรอยู่ที่ไหน

9.1 Method scope

ตัวแปรประกาศใน method → ใช้ได้แค่ใน method นั้น

java
public static void method1() {
    int x = 10;
    System.out.println(x);   // ✅
}

public static void method2() {
    System.out.println(x);   // ❌ error: cannot find symbol
}

9.2 Block scope

ใน if/for/while → ตัวแปรอยู่แค่ใน block

java
public static void main(String[] args) {
    if (true) {
        int x = 10;
    }
    System.out.println(x);   // ❌ error
}

9.3 Loop variable

java
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}
System.out.println(i);   // ❌ error: i อยู่แค่ใน for

9.4 Field (class-level)

ตัวแปรที่ประกาศนอก method แต่ใน class = field — ใช้ได้ทุก method ใน class:

java
public class Counter {
    static int count = 0;   // field
    
    public static void increment() {
        count++;    // ใช้ field ได้
    }
    
    public static void print() {
        System.out.println(count);    // ใช้ field ได้
    }
}

(เดี๋ยวบทที่ 5 จะเรียน field แบบจริงจัง)


10. Recursion — method เรียกตัวเอง

method สามารถ เรียกตัวเอง ได้:

java
public static int factorial(int n) {
    if (n <= 1) return 1;           // base case (จุดหยุด)
    return n * factorial(n - 1);    // recursive case
}

factorial(5);   // 5 * 4 * 3 * 2 * 1 = 120

วิธีคิด:

text
factorial(5)
= 5 * factorial(4)
= 5 * (4 * factorial(3))
= 5 * (4 * (3 * factorial(2)))
= 5 * (4 * (3 * (2 * factorial(1))))
= 5 * (4 * (3 * (2 * 1)))
= 120

⚠️ ต้องมี base case — ไม่งั้น recursion ไม่จบ → StackOverflowError:

java
public static int bad(int n) {
    return bad(n - 1);   // ไม่มี base case!
}

⚠️ Recursion ลึก ๆ มี cost — Java เก็บ frame ใน stack ทุกรอบ ถ้า input ใหญ่ ใช้ loop จะปลอดภัยกว่า

💡 Java ไม่รองรับ tail-call optimization (TCO — เทคนิคที่บางภาษาใช้ลด stack frame ให้ recursion ที่เรียกตัวเองเป็นขั้นตอนสุดท้าย) — recursion ทุกรูปแบบใน Java สร้าง stack frame ใหม่ทุกครั้ง แม้จะเป็น tail-recursive ก็ตาม (บางภาษาอื่นมีระบบนี้ช่วยได้ แต่ Java ไม่มี) ดังนั้นถ้า input ใหญ่ ใช้ loop เสมอ


11. Method ที่ดีต้องเป็นยังไง

11.1 ทำสิ่งเดียว (Single Responsibility)

📝 หมายเหตุ: Order ในตัวอย่างนี้เป็น class สมมติ (pseudo-code เพื่อแสดงไอเดีย) — จะสร้าง class เองได้จริงในบทที่ 5

java
// ❌ ทำหลายอย่าง
// หมายเหตุ: Order เป็น class สมมติ (pseudo-code เพื่อแสดงไอเดีย — จะสร้าง class เองได้ในบทที่ 5)
public static void processOrder(Order order) {
    validate(order);
    calculateTax(order);
    saveToDatabase(order);
    sendEmail(order);
}

// ✅ แยกชัด (Order เป็น class สมมติ — เขียน class เองได้ในบทที่ 5 เช่นเดียวกับส่วน ❌ ด้านบน)
public static boolean validate(Order order) { ... }
public static double calculateTax(Order order) { ... }
public static void saveOrder(Order order) { ... }
public static void sendConfirmationEmail(Order order) { ... }

11.2 ชื่อบอกความหมาย

java
// ❌ ไม่บอกอะไร
public static int doIt(int x) { ... }
public static List<String> getData() { ... }

// ✅ ชัดเจน
public static int calculateAge(Date birthDate) { ... }
public static List<String> findActiveUsers() { ... }

11.3 สั้น — ไม่เกิน 20-30 บรรทัด

ถ้ายาวกว่านี้ → แตกเป็นหลาย method

11.4 Parameter ไม่เยอะเกินไป

  • 0-2 parameter: ดีเยี่ยม
  • 3-4: พอใช้
  • 5+: ❗ อาจต้องสร้าง object รวม
java
// ❌ ยาวเกิน
public static void createUser(String name, String email, int age, 
                              String address, String phone, String country) { ... }

// ✅ ใช้ object
public static void createUser(UserData data) { ... }

(เดี๋ยวบทที่ 5 จะเรียนสร้าง object เอง — ตอนนี้แค่รู้ว่ามีทางอื่นที่อ่านง่ายกว่าการรับ parameter เยอะ ๆ)

11.5 Pure function — ฟังก์ชันที่ test ง่ายและคาดเดาผลลัพธ์ได้

Pure function (ฟังก์ชันบริสุทธิ์) = ฟังก์ชันที่:

  • ไม่มี side effect (ผลกระทบข้างเคียง — การที่ฟังก์ชันเปลี่ยนแปลงสิ่งอื่นนอกจากค่าที่ return เช่น แก้ field, เขียน DB, log, ส่ง email)
  • input เหมือนกัน → output เหมือนกันเสมอ (ไม่อ่าน System.currentTimeMillis(), ไม่ random)
java
// ✅ pure — input → output ตายตัว
public static int discount(int price, int percent) {
    return price * (100 - percent) / 100;
}

// ❌ impure (ฟังก์ชันที่มีผลข้างเคียงหรือผลลัพธ์ขึ้นกับปัจจัยนอก) — มี side effect (มีผลข้างเคียง: log + ใช้เวลาจริง)
public static int discount(int price, int percent) {
    System.out.println("calculating discount");  // side effect (มีผลข้างเคียง)
    long now = System.currentTimeMillis();        // hidden input (ผลขึ้นกับเวลา)
    return price * (100 - percent) / 100;
}

ทำไมสำคัญ: pure function → test ง่าย (ใส่ input แล้วเช็ค output ได้ตรง ๆ), ผลลัพธ์คาดเดาได้ ส่วน impure ก็ยังต้องมี (เช่น เขียน DB หรือ log) — แต่ แยก pure จาก impure ทำให้ code ดูแลง่ายขึ้นมาก

💡 เมื่อเรียน Spring (ภาคหลัง) จะเห็นชัดว่า pure function ช่วยทดสอบ business logic ได้โดยไม่ต้องพึ่ง DB หรือระบบภายนอก

11.6 @Override annotation (คำอธิบายพิเศษที่ใส่หน้า method เริ่มด้วย @ เพื่อบอก compiler) — ใส่ทุกครั้งที่ override

📝 override คือเขียน method ทับของ class แม่ (parent class) — เรื่อง inheritance/parent จะอธิบายในบทที่ 6 ตอนนี้รู้แค่ว่า มี annotation ตัวหนึ่งชื่อ @Override ที่ใส่ไว้บน method ก็พอ

java
class Dog extends Animal {
    @Override                        // ⬅ บอก compiler ว่า "นี่คือ override"
    public void makeSound() { ... }
}

ทำไมต้องใส่ ทั้งที่ Java ไม่บังคับ:

  • typo guard — ถ้าพิมพ์ผิด (makeSounds แทน makeSound) compiler จะ error เพราะ parent ไม่มี method ชื่อนี้
  • signature เปลี่ยน — ถ้า parent เปลี่ยน parameter list, compiler บอกว่า override ขาด

11.7 throws clause (การระบุในลายเซ็น method ว่า method นี้อาจโยน exception ชนิดใดออกมา) — declare checked exception

🚧 อ่านผ่าน ๆ ได้ — ส่วนนี้ใช้คำว่า checked exception ที่จะอธิบายในบทที่ 8 (Exception handling) ตอนนี้รู้แค่ว่า ถ้าเห็นคำว่า throws IOException หลังชื่อ method คือ method นี้อาจ error ตอนทำงานกับไฟล์ ก็พอ

ถ้า method โยน checked exception (ดูบทที่ 8) ต้องประกาศใน signature:

java
// Path และ Files คือเครื่องมืออ่านไฟล์ของ Java (จะใช้จริงในบทที่ 8) — ดูแค่รูปแบบ throws ก็พอ
public String readFile(Path p) throws IOException {     // ⬅ throws อยู่ก่อน `{`
    return Files.readString(p);
}

caller ต้อง catch หรือประกาศ throws ของตัวเอง — compile time check ที่ดีของ Java

⚠️ จุดที่มือใหม่สะดุดบ่อย: checked exception คือ error ที่ compiler บังคับ ให้จัดการ (ต่างจาก error ทั่วไปที่จะเจอตอน run เท่านั้น) — ถ้า method ประกาศ throws IOException แล้ว caller ไม่ใส่ try/catch หรือ throws ต่อ โค้ดจะ compile ไม่ผ่านเลย ไม่ใช่แค่ warning เป็นกฎที่ภาษาโปรแกรมมิ่งบางภาษาไม่มี ดังนั้นถ้าเจอ error แบบนี้ครั้งแรกแล้วงง ให้กลับมาอ่านบทที่ 8 ให้ครบ


12. Checkpoint

🛠️ Checkpoint 4.1 — แยก method

แก้ FizzBuzz จากบทที่แล้วให้ใช้ method getFizzBuzz(int n) ที่ return String

📖 เฉลย
java
public class Main {
    public static String getFizzBuzz(int n) {
        if (n % 15 == 0) return "FizzBuzz";
        if (n % 3 == 0) return "Fizz";
        if (n % 5 == 0) return "Buzz";
        return String.valueOf(n);
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getFizzBuzz(i));
        }
    }
}

🛠️ Checkpoint 4.2 — ฟังก์ชันคณิตศาสตร์

เขียน method:

  • square(int n) — return n²
  • cube(int n) — return n³
  • power(int base, int exp) — return base^exp (ใช้ loop)

ทดสอบใน main ว่าทำงานถูก

📖 เฉลย
java
public class Main {
    public static int square(int n) {
        return n * n;
    }
    
    public static int cube(int n) {
        return n * n * n;
    }
    
    public static int power(int base, int exp) {
        int result = 1;
        for (int i = 0; i < exp; i++) {
            result *= base;
        }
        return result;
    }
    
    public static void main(String[] args) {
        System.out.println(square(5));     // 25
        System.out.println(cube(3));        // 27
        System.out.println(power(2, 10));   // 1024
    }
}

🛠️ Checkpoint 4.3 — Fibonacci

เขียน method fibonacci(int n) return เลข Fibonacci ตัวที่ n

  • fib(0) = 0
  • fib(1) = 1
  • fib(n) = fib(n-1) + fib(n-2)

ลองทั้งแบบ recursion และแบบ loop — เปรียบเทียบ performance ที่ fib(40)

📖 เฉลย
java
public class Main {
    // แบบ recursion — ช้ามากเมื่อ n ใหญ่
    public static long fibRecursive(int n) {
        if (n <= 1) return n;
        return fibRecursive(n - 1) + fibRecursive(n - 2);
    }
    
    // แบบ loop — เร็ว
    public static long fibLoop(int n) {
        if (n <= 1) return n;
        long a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            long temp = a + b;
            a = b;
            b = temp;
        }
        return b;
    }
    
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        System.out.println(fibRecursive(40));
        System.out.println("Recursive: " + (System.currentTimeMillis() - start) + "ms");
        
        start = System.currentTimeMillis();
        System.out.println(fibLoop(40));
        System.out.println("Loop: " + (System.currentTimeMillis() - start) + "ms");
    }
}

ผล: Recursion ใช้เวลา ~1-3 วินาที, Loop < 1ms — เพราะ recursion คำนวณซ้ำซ้อนมาก

🛠️ Checkpoint 4.4 — Overload

เขียน method max หลายแบบ overload:

  • max(int a, int b) — max 2 ตัว
  • max(int a, int b, int c) — max 3 ตัว
  • max(int[] arr) — max ของ array
📖 เฉลย
java
public class Main {
    public static int max(int a, int b) {
        return a > b ? a : b;
    }
    
    public static int max(int a, int b, int c) {
        return max(max(a, b), c);   // reuse!
    }
    
    public static int max(int[] arr) {
        int m = arr[0];
        for (int n : arr) {
            if (n > m) m = n;
        }
        return m;
    }
    
    public static void main(String[] args) {
        System.out.println(max(5, 10));         // 10
        System.out.println(max(5, 10, 3));      // 10
        System.out.println(max(new int[]{3, 1, 4, 1, 5, 9, 2, 6}));  // 9
    }
}

13. สรุปบท

✅ Method = "ฟังก์ชัน" — รับ input, ทำงาน, return output (หรือ void) ✅ Syntax: [modifier] returnType methodName(params) { body } ✅ Java pass by value เสมอ — แต่ reference type จะ copy reference (modify ของจริงได้) ✅ Overload = ชื่อเดียว parameter ต่าง ✅ Varargs (...) — รับ argument หลายตัว ✅ Scope: ตัวแปรอยู่ใน block ที่ประกาศ ✅ Recursion = method เรียกตัวเอง — ต้องมี base case ✅ Method ที่ดี: สั้น, ทำสิ่งเดียว, ชื่อชัด, parameter ไม่เยอะ

→ ไปบทที่ 5: OOP พื้นฐาน