โหมดมืด
บทที่ 3 — Control Flow (if, switch, loop)
โปรแกรมจริง ๆ ไม่ได้แค่ "run บรรทัด 1, 2, 3, จบ" — มันต้อง ตัดสินใจ (ถ้า... ทำสิ่งนี้, ไม่ใช่... ทำอีกสิ่ง) และ ทำซ้ำ (ทำ 10 ครั้ง, ทำจนกว่า user จะกด exit)
นี่คือ control flow — การควบคุมว่า "บรรทัดไหนจะ run, บรรทัดไหนจะไม่ run, บรรทัดไหนจะ run กี่ครั้ง"
หลังจบบทนี้ คุณจะ:
- ใช้
if,else if,elseตัดสินใจได้ - ใช้
switch(ทั้งแบบเก่าและ Java 21+ style) - เขียน loop ด้วย
for,while,do-while,for-each - ใช้
breakกับcontinueควบคุม loop - เข้าใจ "ทำไม nested condition ลึก ๆ ไม่ดี" + วิธีลด nesting
1. if-else
1.1 แบบง่ายสุด
java
int age = 20;
if (age >= 18) {
System.out.println("Adult");
}อ่านได้ว่า: "ถ้า age >= 18 เป็นจริง ให้ทำใน block {...}"
ในวงเล็บต้องเป็น boolean (true/false) — Java ห้ามใช้ int (ต่างจาก C)
1.2 if-else
java
if (age >= 18) {
System.out.println("Adult");
} else {
System.out.println("Minor");
}"ถ้าจริงทำ A, ไม่จริงทำ B"
1.3 if-else if-else (multi-way)
java
int score = 75;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else if (score >= 70) {
System.out.println("C");
} else if (score >= 60) {
System.out.println("D");
} else {
System.out.println("F");
}Java จะ เช็คจากบนลงล่าง หยุดที่อันแรกที่เป็น true
1.4 ⚠️ Pitfall — ลืม { }
java
// ผิด แต่ compile ผ่าน!
if (age >= 18)
System.out.println("Adult");
System.out.println("Welcome!"); // ❌ ไม่อยู่ใน if! print เสมอถ้าไม่ใส่ { } Java จะถือว่า if มีแค่ statement ถัดไปบรรทัดเดียว
กฎ rule of thumb: ใส่ { } เสมอ แม้มี statement เดียว — กัน bug + อ่านง่าย
1.5 ⚠️ Pitfall — == กับ =
java
if (age = 18) { ... } // ❌ error: ไม่ใช่ boolean (= คือ assignment)
if (age == 18) { ... } // ✅Java เซฟกว่า C ตรงนี้ — compile error ทันที (C compile ผ่าน, run แล้วผิด)
1.6 Nested if
java
if (isLoggedIn) {
if (isAdmin) {
if (hasPermission) {
// ทำอะไร...
}
}
}อ่านยาก → ใช้ early return หรือ && แทน:
java
// แบบ &&
if (isLoggedIn && isAdmin && hasPermission) {
// ทำอะไร...
}
// แบบ early return (ในmethod)
// (การประกาศ method เรียนบทที่ 4 — ตอนนี้ดูแค่ไอเดีย "return เร็ว")
public void doSomething() {
if (!isLoggedIn) return;
if (!isAdmin) return;
if (!hasPermission) return;
// ทำอะไร... (main logic)
}💡 Best practice: ลด nesting โดยเช็ค "เคสที่ไม่ทำ" ก่อนแล้ว return — ลด indent ได้เยอะ อ่านง่ายขึ้น
2. switch
2.1 switch แบบเก่า (classic)
java
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
default:
System.out.println("Unknown");
}วิเคราะห์:
switch (day)— เช็คค่าของdaycase 1:— ถ้าday == 1break;— สำคัญมาก — ออกจาก switch ถ้าไม่ใส่จะ "fall through" ลงไปทำ case ถัดไปด้วยdefault:— เคสที่ไม่ match อะไรเลย (เหมือน else)
2.2 ⚠️ Pitfall — ลืม break (fall-through)
java
switch (day) {
case 1:
System.out.println("Monday");
// ลืม break!
case 2:
System.out.println("Tuesday");
break;
}
// day = 1 → print "Monday" + "Tuesday" 😱นี่คือเหตุผลที่ switch แบบเก่ามีปัญหา — ลืม break ได้ง่ายมาก ภาษา Java ใหม่จึงมีแบบที่ดีกว่า ดู section 2.3
2.3 switch แบบใหม่ (Java 14+) — Switch Expression
java
String dayName = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
case 4, 5 -> "Late week"; // หลายค่าได้
default -> "Unknown";
};ข้อดี:
- ใช้
->แทน:+ ไม่ต้อง break - เป็น expression — return ค่ากลับเป็นตัวแปรได้
- บังคับ exhaustive (ครอบคลุมทุกกรณี) — switch expression ต้องครอบคลุมทุกเคสหรือมี
defaultไม่งั้น compile error (switch statement แบบเก่าที่ใช้:+breakไม่บังคับ) - หลายค่าใน case เดียว:
case 4, 5 ->
ขั้นสูง — enum และ sealed (ข้ามได้รอบแรก)
enum และ sealed type คือ class แบบพิเศษที่กำหนดค่าได้ครบทุกกรณีตายตัว — ถ้าใช้กับ switch expression และ cover ครบทุกค่า compiler จะไม่บังคับให้มี default (เดี๋ยวบทที่ 10 จะอธิบายละเอียด)
2.4 Switch กับ block (มีหลาย statement)
java
String result = switch (day) {
case 1 -> "Monday";
case 2 -> {
System.out.println("Tuesday processing...");
yield "Tuesday"; // ใช้ yield แทน return
}
default -> "Unknown";
};yield = "return จาก block ของ switch expression"
2.4.5 case null + case null, default (Java 21+)
ก่อน Java 21 — switch ที่รับ Object แล้ว object เป็น null → throw NullPointerException ทันที (ก่อนเข้าทุก case)
Java 21+: รับ null ได้:
java
Object obj = null;
String result = switch (obj) {
case null -> "null";
case String s -> "string: " + s;
case Integer i -> "int: " + i;
default -> "other";
};หรือรวม null กับ default:
java
String result = switch (obj) {
case String s -> "string: " + s;
case Integer i -> "int: " + i;
case null, default -> "null หรืออื่น ๆ";
};⚠️ ถ้า switch มี case ใด ๆ ที่เป็น Object pattern (
String s,Integer i, ฯลฯ) แล้วไม่มีcase nullหรือcase null, default— null ยัง throw NPE เหมือนเดิม (backward compatible)
2.5 Pattern matching switch (Java 21+ stable)
แอบดูล่วงหน้า — เปิดดูหลังเรียนบทที่ 6 (object/polymorphism)
Java 21+ มีของใหม่ชื่อ pattern matching switch ที่เช็ค type ของ object แล้วทำงานตามชนิดที่เจอได้ — แต่ใช้คอนเซปต์ object/cast/polymorphism ที่ยังไม่ได้เรียน
ตอนนี้รู้แค่ว่า มี feature นี้อยู่ ก็พอ — เดี๋ยวบทที่ 10 จะเล่าละเอียดพร้อมตัวอย่างที่อ่านเข้าใจง่าย
2.6 เมื่อไหร่ใช้ if vs switch
| สถานการณ์ | ใช้อะไร |
|---|---|
| 2-3 condition | if-else |
| 4+ condition แบบ "ค่านี้ตรงกับอะไร" | switch |
Range เช่น if (x > 100) | if-else |
| ผสมหลาย field | if-else |
3. Loop
3.1 while
ทำซ้ำตราบใดที่ condition เป็น true:
java
int count = 0;
while (count < 5) {
System.out.println("Count: " + count);
count++;
}
// print: 0, 1, 2, 3, 4⚠️ Infinite loop — ถ้าลืมเพิ่ม count:
java
int count = 0;
while (count < 5) {
System.out.println("Count: " + count);
// ลืม count++!
}
// loop ไม่จบ — กด Ctrl+C ใน terminal เพื่อหยุด (ถ้า run ใน IntelliJ ให้กดปุ่ม Stop ■ สีแดงที่ panel Run)3.2 do-while
เหมือน while แต่ ทำอย่างน้อย 1 ครั้ง เสมอ:
java
int x = 100;
do {
System.out.println("Running");
x++;
} while (x < 5);
// print "Running" 1 ครั้ง (เพราะ x = 100 ตั้งแต่ต้น แต่ทำก่อนเช็ค)ต่างกับ while:
java
int y = 100;
while (y < 5) {
System.out.println("Running");
y++;
}
// ไม่ print เลย (condition false ตั้งแต่แรก)ใช้น้อย — เกือบทุกกรณีใช้ while ก็ได้
3.3 for loop — แบบ classic
java
for (int i = 0; i < 5; i++) {
System.out.println("i = " + i);
}
// print: 0, 1, 2, 3, 4วิเคราะห์ 3 ส่วน:
- Init:
int i = 0— run ครั้งเดียว ตอนเริ่ม - Condition:
i < 5— เช็คทุกรอบก่อนทำ - Update:
i++— run หลังจบแต่ละรอบ
ลำดับ execution:
text
init → condition (true?) → body → update → condition → body → update → ... → condition (false) → exitเปรียบเทียบกับ while
java
// for
for (int i = 0; i < 5; i++) {
body();
}
// while ที่เทียบเท่า
int i = 0;
while (i < 5) {
body();
i++;
}For จะ compact กว่า — เหมาะเมื่อรู้จำนวนรอบล่วงหน้า
3.4 ตัวอย่าง for ที่ใช้บ่อย
java
// นับถอยหลัง
for (int i = 10; i > 0; i--) { ... }
// ทีละ 2
for (int i = 0; i < 100; i += 2) { ... }
// loop ทุก index ของ array
int[] arr = {10, 20, 30};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}3.5 for-each loop
ถ้าไม่ต้องการ index — แค่อยาก iterate ทุก element:
java
int[] arr = {10, 20, 30};
for (int n : arr) {
System.out.println(n);
}อ่านได้ว่า: "สำหรับแต่ละ n ใน arr"
ใช้กับ array, List, Set, Map ได้
java
// บรรทัดนี้วางไว้ต้นไฟล์ ก่อน public class Main { ... }
import java.util.List;java
// List = รายการข้อมูล — เรียนละเอียดบทที่ 7 ตอนนี้ดูแค่รูปแบบ for-each
// (IDE เช่น IntelliJ จะ auto-import ให้อัตโนมัติ — กด Alt+Enter บน List ที่ขีดแดง)
List<String> names = List.of("Anna", "Ben", "Cal");
for (String name : names) {
System.out.println(name);
}⚠️ ข้อจำกัด: for-each ไม่รู้ index → ถ้าต้องใช้ index ใช้ classic for
⚠️ ไม่ modify collection ระหว่าง for-each — อย่าเพิ่ม/ลบ item ใน list ขณะที่ for-each กำลังวนอยู่ ถ้าทำจะเกิด error พิเศษชื่อ ConcurrentModificationException (exception = error ตอน run — เดี๋ยวบทที่ 8 จะอธิบาย, ตอนนี้รู้แค่ว่าอย่าทำแบบนี้ก็พอ) — กฎเดียวกันนี้ใช้กับ Map.keySet()/entrySet() ตอน iterate ด้วย (เดี๋ยวบทที่ 7 จะเจอ)
4. break และ continue
4.1 break — ออกจาก loop ทันที
java
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // ออกทันที
}
System.out.println(i);
}
// print: 0, 1, 2, 3, 44.2 continue — ข้ามรอบนี้ ไปรอบถัดไป
java
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // ข้ามถ้าคู่
}
System.out.println(i);
}
// print: 1, 3, 5, 7, 94.3 Labeled break (loop ซ้อน)
label คือชื่อที่เราตั้งให้ loop เพื่อระบุว่าอยาก break หรือ continue loop ไหน — เขียนด้วยชื่อตามด้วย : วางไว้ก่อน for
java
outer:
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i * j > 6) {
break outer; // break ออกจาก loop ภายนอกเลย
}
System.out.println(i + "," + j);
}
}ใช้น้อย — ถ้าซ้อนลึกแบบนี้ลอง refactor เป็น method ก่อน
5. Pattern ที่ใช้บ่อย
5.1 Sum หา min/max
java
int[] numbers = {3, 1, 4, 1, 5, 9, 2, 6};
int sum = 0;
for (int n : numbers) {
sum += n;
}
System.out.println("Sum: " + sum);
int max = numbers[0];
for (int n : numbers) {
if (n > max) max = n;
}
System.out.println("Max: " + max);5.2 Count
java
int count = 0;
for (int n : numbers) {
if (n > 3) count++;
}
System.out.println("Numbers > 3: " + count);5.3 Linear search
java
int target = 5;
int foundIndex = -1;
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == target) {
foundIndex = i;
break;
}
}
if (foundIndex >= 0) {
System.out.println("Found at index " + foundIndex);
} else {
System.out.println("Not found");
}5.4 Building string
java
// (fragment — ต้องอยู่ใน class และ method ก่อนนำไปรัน)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append("Item ").append(i).append(", ");
}
String result = sb.toString();⚠️ อย่าใช้ + ใน loop — ช้ามาก เพราะ String immutable ทุกครั้งสร้างใหม่ ใช้ StringBuilder แทน
6. Checkpoint
🛠️ Checkpoint 3.1 — FizzBuzz
โจทย์คลาสสิก: print 1-100, ถ้าหารด้วย 3 ลงตัวพิมพ์ "Fizz", หารด้วย 5 ลงตัวพิมพ์ "Buzz", หารทั้งคู่ลงตัวพิมพ์ "FizzBuzz", ไม่งั้นพิมพ์เลข
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
if (i % 15 == 0) { // 3 และ 5 ลงตัว = 15 ลงตัว
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.println(i);
}
}
}
}สังเกต: เช็ค % 15 ก่อน เพราะถ้าเช็ค % 3 ก่อน เลข 15 จะตก case "Fizz" ก่อน
🛠️ Checkpoint 3.2 — Sum 1 ถึง N
รับ N จาก args[0] แล้วหา sum 1+2+...+N ทดสอบ: java Main 100 → 5050
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
System.out.println(sum);
}
}🛠️ Checkpoint 3.3 — เช็ค prime
รับ N จาก args[0] ตรวจว่า prime หรือไม่ ใบ้: ถ้า N หารด้วยตัวเลข 2 ถึง N-1 ไม่ลงตัวเลย = prime ปรับให้เร็วขึ้น: เช็คแค่ถึง Math.sqrt(N) พอ (Math.sqrt(n) = หารากที่สอง — class Math มากับ Java เรียกได้เลยไม่ต้อง import)
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
if (n < 2) {
System.out.println(n + " is not prime");
return;
}
boolean isPrime = true;
for (int i = 2; i * i <= n; i++) { // i*i <= n เทียบเท่า i <= sqrt(n) และเร็วกว่า (ไม่ต้อง call sqrt ทุกรอบ)
if (n % i == 0) {
isPrime = false;
break;
}
}
System.out.println(n + " is " + (isPrime ? "prime" : "not prime"));
}
}🛠️ Checkpoint 3.4 — เกรดจาก switch
ใช้ switch expression (สไตล์ใหม่) แปลงคะแนนเป็นเกรด:
- 90+: A
- 80-89: B
- 70-79: C
- 60-69: D
- ต่ำกว่า: F
ใบ้: หาร score ด้วย 10 แล้ว switch เลขผลลัพธ์ (10/9 → A, 8 → B, ...) — หรือใช้ if-else ก็ได้
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
int score = Integer.parseInt(args[0]);
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> "F";
};
System.out.println("Grade: " + grade);
}
}เทคนิค: หารด้วย 10 → ทำให้ 95 กลายเป็น 9, 85 กลายเป็น 8 — ใช้ switch ได้สะดวก
7. สรุปบท
✅ if-else ตัดสินใจตาม condition (boolean) ✅ switch เหมาะกับ "เทียบค่าตรง ๆ" หลาย ๆ ค่า — Java 14+ มี switch expression ✅ while / do-while / for / for-each — เลือกตามสถานการณ์ ✅ break ออกจาก loop, continue ข้ามรอบนี้ ✅ ลด nesting ด้วย early return + && ✅ ใช้ StringBuilder แทน + ใน loop