โหมดมืด
บทที่ 4 — Methods และ Scope
จนถึงตอนนี้ทั้งโปรแกรมเราอยู่ใน main method เดียว — โปรแกรมจริงไม่ทำแบบนั้น
Method = หน่วยย่อยของโปรแกรม ที่ "ตั้งชื่อ" + "นำกลับมาใช้ใหม่ได้"
หลังจบบทคุณจะ:
- สร้าง method + เรียกใช้ + return ค่า
- เข้าใจ parameter (pass by value)
- ใช้ method overloading
- เข้าใจ scope ของตัวแปร
- รู้จัก
staticvs 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;
}แยกเป็นชิ้น:
| ส่วน | ความหมาย |
|---|---|
public | access modifier (ตัวกำหนดการเข้าถึง) — ใครสามารถเรียก method นี้ได้ |
static | method ของ class (ไม่ใช่ของ object) — เดี๋ยวจะเข้าใจในบทที่ 5 |
int | return 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 ไปใส่ใน n — n เป็นตัวแปรใหม่ คนละตัวกับ 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] ←── arr4.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 method | Integer::parseInt | s -> Integer.parseInt(s) | ใช้เมื่อต้องการเรียก static method โดยตรง |
| Bound instance (instance ที่กำหนดตายตัว) | System.out::println | x -> System.out.println(x) | ใช้เมื่อ object ที่จะเรียก method ถูกกำหนดแล้ว |
| Unbound instance (instance มาจาก argument ที่ส่งเข้ามา) | String::toUpperCase | s -> s.toUpperCase() | ใช้เมื่อ method เรียกจาก argument ที่รับเข้ามา |
| Constructor | ArrayList::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 อยู่แค่ใน for9.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 ไม่เยอะ