โหมดมืด
บทที่ 2 — ตัวแปร, ประเภทข้อมูล, และ Operator
บทนี้คือ พื้นฐานของพื้นฐาน — ทุกโปรแกรมไม่ว่าใหญ่แค่ไหนก็เริ่มจาก "เก็บข้อมูล" และ "คำนวณ"
หลังจบบท คุณจะ:
- เข้าใจว่า "ตัวแปร" คืออะไรจริง ๆ (ไม่ใช่แค่ "กล่อง")
- รู้จัก type ทั้ง 8 ตัวของ primitive + String
- ใช้ operator ทำการคำนวณ + ระวัง trap ของแต่ละตัว
- เข้าใจ
var(type inference) และเมื่อไหร่ควรใช้ - ระวัง overflow, type conversion, integer division
💡 ไม่ต้องท่องชื่อ feature — แค่อ่านผ่านครั้งแรก เดี๋ยวเจอจริงจะกลับมาดู
1. ตัวแปรคืออะไร — ภาพที่ถูกต้อง
หลายคนสอน: "ตัวแปร = กล่องเก็บข้อมูล"
ภาพนี้ผิดครึ่งหนึ่ง — ภาพที่ถูกกว่าคือ:
ตัวแปร = ป้ายชื่อ ที่ชี้ไปที่ memory location หนึ่ง ๆ ที่เก็บข้อมูล
ลองนึกถึงตู้ล็อกเกอร์ในสนามกีฬา:
- ตู้แต่ละช่องคือ "memory location" — มีหมายเลขเฉพาะ (address)
- ในตู้แต่ละช่องเก็บของ (data)
- "ป้ายชื่อ" ที่ติดบนตู้คือ "ตัวแปร" — บอกว่า "ของของฉันอยู่ตู้ไหน"
ทำไมภาพนี้สำคัญ:
- เพราะตอนเรา assign ค่า (
x = 5) — Java เก็บค่าใน memory แล้วผูกชื่อxกับค่านั้น - ตอน ส่งตัวแปรเข้า method — Java จะ copy ค่า (สำหรับ primitive) หรือ copy ป้ายชี้ (สำหรับ reference) ไม่ใช่ "ขนของไปด้วย"
⚠️ ตอนนี้ขอแค่จำว่า: "ตัวแปรคือชื่อที่ผูกกับค่าหนึ่ง ๆ" — รายละเอียดเรื่อง primitive vs reference อยู่ใน 1.1 ข้างล่าง
1.1 🐍 ถ้ามาจาก Python/JS — Java มี 2 โลก ที่ต่างจากภาษาเดิม
🔎 ลึกกว่านี้ — ข้ามได้รอบแรก ส่วนนี้เปรียบเทียบกับ Python/JS ถ้าไม่เคยใช้สองภาษานี้หรืออยากเข้าใจตัวแปรจริง ๆ ก่อน ข้ามไป section 2 (ประกาศตัวแปร) ได้เลย เดี๋ยวกลับมาอ่านได้
⚠️ หมายเหตุ: แนวคิด "primitive vs reference" ใน section นี้จำเป็นต้องใช้อีกครั้งใน section 4.2.1 (Integer Cache) และ section 5 (String) ซึ่งอยู่ ในบทเดียวกันนี้เอง (ไม่ใช่บทถัดไป)
ถ้าข้ามส่วนนี้ไปตอนนี้ แล้วไปงงเรื่อง
==กับ wrapper ตอนอ่าน 4.2.1 หรือ 5 — ให้ย้อนกลับมาอ่าน section 1.1 นี้ก่อนแล้วค่อยไปต่อ
ใน Python/JS ทุกอย่างเป็น object — ไม่มี "primitive" แยก ใน Java แยก 2 โลก:
📝 stack / heap คืออะไร: stack = หน่วยความจำสำหรับ local variable และ reference ของ object ในแต่ละ method call, heap = หน่วยความจำใหญ่กว่าที่เก็บ object — เดี๋ยวบทที่ 16 อธิบายลึก ตอนนี้รู้แค่ว่ามี 2 ที่ก็พอ
โลก 1 — primitive (เก็บค่าตรง ๆ ใน stack):
xเก็บค่า 5 ตรง ๆ ใน stack — ไม่ใช่ป้ายชี้ ไม่มีอะไรเพิ่ม
โลก 2 — reference (ตัวแปรเป็นป้ายชี้ไป object ใน heap):
sเก็บแค่ ที่อยู่ ของ object — ตัว"Hello"จริง ๆ อยู่ใน heap
ASCII version (เผื่ออยากเทียบ memory layout จริง ๆ)
text
┌────────────────────── stack (เร็ว, เล็ก) ──────────────────────┐
│ int x = 5 │
│ ┌───┐ │
│ │ 5 │ ← x เก็บค่า 5 ตรง ๆ (primitive — ไม่ใช่ reference) │
│ └───┘ │
└──────────────────────────────────────────────────────────────────┘
┌────── stack ──────┐ ┌────── heap (object เก็บที่นี่) ──────┐
│ String s │ │ │
│ ┌─────┐ │ │ ┌──────────────────┐ │
│ │ ●───┼──────────┼─────┼─►│ "Hello" object │ ← ของจริง │
│ └─────┘ │ │ └──────────────────┘ │
│ s = ป้ายชี้ │ │ │
│ (reference) │ │ │
└───────────────────┘ └───────────────────────────────────────┘| โลก | ตัวอย่าง | เก็บอะไรในตัวแปร |
|---|---|---|
| primitive (8 ตัว) | int, long, double, boolean, char, byte, short, float | ค่าจริง ตรง ๆ |
| reference (ทุก class) | String, Integer, array (เช่น int[]), List, ตัวเองที่เราเขียน | ป้ายชี้ ไป object ใน heap |
Python/JS เทียบ:
- Python: ทุกอย่างเป็น object → ทุกตัวแปรเป็น "reference" ทั้งหมด — ไม่มี primitive
- JS: มี primitive (number, string, boolean, ...) แต่ไม่บังคับประกาศ type — Java บังคับ
- ผลคือ: ใน Java
int x = 5กับInteger x = 5ดูเหมือนกันแต่ เก็บคนละแบบ — รายละเอียดใน section 4.2.1
ตอนนี้รู้แค่ว่า "Java มี 2 โลก" ก็พอ
2. ประกาศตัวแปร — Syntax พื้นฐาน
ใน Java มี syntax 2 แบบ:
แบบ 1: บอก type ตรง ๆ
java
int age = 25;แยกชิ้น:
int= type — บอกว่าจะเก็บอะไร (จำนวนเต็ม)age= ชื่อตัวแปร — เราตั้งเอง== assignment operator — "กำหนดค่า"25= value — ค่าที่จะเก็บ;= ปิด statement
อ่านออกเสียง: "ประกาศตัวแปรชื่อ age ประเภท int ให้มีค่า 25"
แบบ 2: ประกาศก่อน, assign ทีหลัง
java
int age; // ประกาศตัวแปร (ยังไม่ใส่ค่า)
age = 25; // assign ทีหลังหลังประกาศแต่ยังไม่ assign — ตัวแปร "มีอยู่" แต่ยังไม่มีค่า:
java
int age;
System.out.println(age); // ❌ error: variable age might not have been initializedJava ระวังเรื่องนี้มาก — บังคับเรา assign ก่อนใช้เสมอ
แบบ 3: ใช้ var (Java 10+)
java
var age = 25;var = บอก compiler ให้เดา type เอง — compiler จะดูจากค่าด้านขวา (25 = int) แล้วตั้ง type ให้
อ่านได้ว่า: "variable นี้ type อะไรก็ได้ ปล่อย compiler คิด"
⚠️ ข้อจำกัดของ var:
- ใช้ได้แค่ ในตัว method เท่านั้น — ไม่ใช้นอก method (เรื่อง field ใน class จะอธิบายบทที่ 5)
- ต้อง assign ค่าทันที —
var x;ไม่ได้ (compiler เดา type ไม่ออก) - ห้าม assign
nullทันที —var x = null;ไม่ได้ (compiler ไม่รู้ type)
ตัวอย่างที่ var มีประโยชน์:
java
// แบบเก่า — เขียน type ซ้ำทั้งด้านซ้ายและขวา
BigDecimal price = new BigDecimal("19.99");
// แบบ var — อ่านสบายตา (compiler เดา type จาก new BigDecimal(...) ทางขวา)
var price = new BigDecimal("19.99");💡
varมีประโยชน์มากเมื่อ type ด้านขวายาว เช่น collection ที่เรียนในบทที่ 7 — แต่ตอนนี้ใช้กับ type ที่รู้จักแล้วก็ได้
💡 กฎ rule of thumb: ใช้
varเมื่อ type ด้านขวา ชัดเจน จากบริบท — ถ้าอ่านยากให้เขียน type ตรง ๆ
3. กฎตั้งชื่อตัวแปร
กฎเข้มงวด (compile error ถ้าผิด)
📝 compile error vs runtime error: compile error = IDE/compiler บอกผิด ก่อน run โปรแกรมด้วยซ้ำ (สีแดงใน IDE); runtime error = ตอน run แล้วเจอ error กลางคัน
- ขึ้นต้นด้วยตัวอักษร,
_, หรือ$(ไม่ขึ้นด้วยตัวเลข) - ใช้ตัวอักษร, ตัวเลข,
_,$ได้ - ห้ามใช้ reserved keyword (
int,class,if, ...) - case-sensitive —
age≠Age
Convention (ทุกคนทำตาม — แม้ไม่ใช่ error)
- camelCase — ขึ้นต้นด้วยตัวเล็ก, คำใหม่ใช้ตัวใหญ่
- ✅
firstName,userAge,totalAmount - ❌
first_name,FirstName,firstname
- ✅
- descriptive — ชื่อบอกความหมายชัดเจน
- ✅
userAge,cartTotal - ❌
x,data,tmp(ยกเว้น scope สั้นมาก)
- ✅
- boolean — ขึ้นต้นด้วย
is,has,can- ✅
isActive,hasPermission,canEdit
- ✅
Reserved keywords ที่ห้ามใช้เป็นชื่อ
text
abstract continue for new switch
assert default if package synchronized
boolean do goto* private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const* float native super while(* const กับ goto reserved แต่ Java ไม่ได้ใช้จริง)
📝 Restricted keywords ใน Java ใหม่:
var,record,yield,sealed,permitsเป็น "context-sensitive keywords" — ไม่ใช่ reserved keyword เต็มรูปแบบ แต่ควรหลีกเลี่ยงใช้เป็นชื่อตัวแปร เพราะ IDE อาจแจ้ง warning หรือ error ในบางบริบท
4. Primitive Types — ประเภทพื้นฐาน 8 ตัว
Java มี primitive type ที่ built-in 8 ตัว — เก็บ "ค่าจริง" ตรง ๆ ไม่ใช่ reference
📝 primitive type คืออะไร: type พื้นฐานที่ Java สร้างมาให้พร้อมใช้ เก็บค่าตรง ๆ ไม่ใช่ object (ไม่มี method ให้เรียก, ไม่มี
null)
4.1 จำนวนเต็ม (Integer types)
| Type | ขนาด | ช่วง | เมื่อไหร่ใช้ |
|---|---|---|---|
byte | 1 byte (8 bit) | -128 ถึง 127 | binary data, network protocol |
short | 2 byte | -32,768 ถึง 32,767 | ไม่ค่อยใช้ |
int | 4 byte | ±2.1 พันล้าน | default สำหรับจำนวนเต็ม |
long | 8 byte | ±9.2 ล้านล้านล้าน | จำนวนใหญ่ มากกว่า 2.1 พันล้าน |
ทำไมต้องมีหลายตัว: ประหยัด memory — สมัยก่อน RAM แพง, ใช้ byte แทน int ประหยัด 4 เท่า
ปี 2026 RAM ถูก → ใช้ int เป็นหลัก, ใช้ long เมื่อจำเป็น
ตัวอย่าง
java
int age = 25;
int year = 2026;
int population = 1_400_000_000; // ใช้ _ คั่นได้เพื่ออ่านง่าย (Java 7+)
long worldPopulation = 8_000_000_000L; // L ตอนท้าย — บอกว่าเป็น long ไม่ใช่ int
byte b = 100; // OK
byte tooBig = 200; // ❌ error: 200 เกิน 127
short s = 30000;⚠️ Pitfall: literal เป็น int เสมอ — ต้องใส่ L สำหรับ long
java
long bigNumber = 3_000_000_000; // ❌ error: integer number too large
long bigNumber = 3_000_000_000L; // ✅ มี L บอก compiler ว่าเป็น longNumber literal — เขียนได้หลาย base
🟡 ขั้นกลาง — ข้ามได้รอบแรก ส่วนนี้เป็นความสามารถเสริม ไม่ต้องใช้ในงานทั่วไป
java
int numDec = 255; // decimal — default
int numHex = 0xFF; // hexadecimal (16) — เริ่ม 0x หรือ 0X
int numBinary = 0b1111_1111; // binary (2) — Java 7+, เริ่ม 0b หรือ 0B
int numOctal = 0377; // octal (8) — เริ่ม 0 ⚠️ คนเข้าใจผิดบ่อย
// numDec == numHex == numBinary == numOctal == 255
System.out.println(numDec + " " + numHex + " " + numBinary + " " + numOctal); // 255 255 255 255
// underscore แทรกอ่านง่าย (Java 7+)
long credit = 1234_5678_9012_3456L; // เลขบัตร 16 หลัก (กลุ่มละ 4)
int perm = 0b110_100_100; // 0644 (rw-r--r--)
int color = 0xFF_88_44; // RGB
// ❌ ใส่ขอบ: _100, 100_, 0_x, 0x_FF — ไม่ได้⚠️ Octal
0-prefix เป็นมรดกจาก C —int x = 010;คือ 8 ไม่ใช่ 10 (อย่าใช้ leading zero ในเลข decimal)
⚠️ Pitfall ที่ควรรู้แม้ข้ามส่วนด้านบน:
int x = 010;คือ 8 (octal) ไม่ใช่ 10 — เลขที่ขึ้นต้นด้วย0ใน Java คือ octal เสมอ อย่าใช้ leading zero (เลข 0 นำหน้าตัวเลข เช่น010,007) กับเลข decimal
⚠️ Pitfall: integer overflow — ไม่ error แต่ค่าผิด
java
int max = 2_147_483_647; // Integer.MAX_VALUE
int wrapped = max + 1;
System.out.println(wrapped); // -2,147,483,648 (วน!)Java ไม่ throw error ตอน overflow — แค่ wrap around กลับไปต่ำสุด
ถ้ากลัว overflow → ใช้ Math.addExact(a, b) จะ throw exception เมื่อ overflow
4.2 ทศนิยม (Floating-point types)
| Type | ขนาด | precision (ความแม่นยำ) | เมื่อไหร่ใช้ |
|---|---|---|---|
float | 4 byte | ~7 หลัก | ประหยัด memory, graphics |
double | 8 byte | ~15 หลัก | default สำหรับทศนิยม |
ตัวอย่าง
java
double price = 199.99;
double pi = 3.14159265358979;
float temperature = 36.5f; // f ตอนท้าย — บอกว่าเป็น float ไม่ใช่ double⚠️ Pitfall: literal ทศนิยมเป็น double เสมอ
java
float bad = 3.14; // ❌ error: lossy conversion from double to float
float good = 3.14f; // ✅⚠️⚠️ Pitfall ใหญ่: ทศนิยมไม่แม่นยำเสมอ
java
double x = 0.1 + 0.2;
System.out.println(x); // 0.30000000000000004 (ไม่ใช่ 0.3 เป๊ะ!)นี่ไม่ใช่ bug ของ Java — เป็น "limitation" ของ floating-point ใน CPU ถ้าต้องการความแม่นยำ (เช่น เงิน) ให้ใช้ BigDecimal แทน:
📝
importคืออะไร: บอก Java ว่าจะใช้ class จาก library อื่น — ต้องพิมพ์ที่บรรทัดบนสุดของไฟล์ ก่อนpublic classถ้าใช้ IntelliJ ให้กดAlt+EnterบนBigDecimalแล้ว IDE จะเติม import ให้อัตโนมัติ (เดี๋ยวบทที่ 5 อธิบายละเอียด)
java
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 0.3 เป๊ะ
}
}📝
BigDecimal.toString()เก็บ scale (จำนวนตำแหน่งทศนิยม) ตามที่คำนวณได้จริง ไม่ได้ตัดเลข 0 ท้ายให้เสมอ — เช่นnew BigDecimal("0.10").add(new BigDecimal("0.20"))จะได้"0.30"ไม่ใช่"0.3"ถ้าต้องการตัดเลข 0 ท้ายออก ใช้.stripTrailingZeros()
⚠️ ใช้ string ใน constructor ของ
BigDecimalเสมอ —new BigDecimal(0.1)จะได้0.1000000000000000055511151231257827021181583404541015625(ค่าที่ double เก็บจริง ๆ ไม่ใช่ 0.1)💡 ถ้ามีตัวแปร
doubleอยู่แล้ว ใช้BigDecimal.valueOf(d)แทนnew BigDecimal(d)—valueOfใช้ canonical string representation จึงปลอดภัย เช่นBigDecimal.valueOf(0.1)ให้ผลเหมือนnew BigDecimal("0.1")
BigDecimal: scale + RoundingMode (อ้างอิงตอนทำเรื่องเงินจริง)
🟡 ส่วนนี้เก็บไว้กลับมาดูตอนทำงาน DB/บัญชี/เงินจริง ๆ — รอบแรกข้ามได้
java
// ต้อง import ที่ต้นไฟล์ก่อน public class:
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal price = new BigDecimal("19.99");
BigDecimal qty = new BigDecimal("3");
BigDecimal total = price.multiply(qty); // 59.97
// หาร — ต้องระบุ scale + RoundingMode ไม่งั้น throw ArithmeticException ถ้าหารไม่ลงตัว
BigDecimal each = total.divide(new BigDecimal("7"), 2, RoundingMode.HALF_UP);
// 59.97 / 7 = 8.566... → scale=2 + HALF_UP → 8.57
// scale = จำนวนตำแหน่งหลังจุดทศนิยม
// setScale() คืน object ใหม่ ต้องรับไว้ใน variable (BigDecimal immutable)
BigDecimal rounded = total.setScale(2, RoundingMode.HALF_UP); // 59.97 (มี 2 ตำแหน่ง)
BigDecimal rounded0 = total.setScale(0, RoundingMode.HALF_UP); // 60
// เปรียบเทียบ: ใช้ compareTo ไม่ใช่ equals
boolean eq1 = new BigDecimal("1.0").equals(new BigDecimal("1.00")); // false (scale ต่าง!)
boolean eq2 = new BigDecimal("1.0").compareTo(new BigDecimal("1.00")) == 0; // true
System.out.println(eq1 + " / " + eq2); // false / trueRoundingMode ที่ใช้บ่อย:
HALF_UP— ปกติ (0.5 → 1) — default ของวิชาคณิตศาสตร์HALF_EVEN— banker's rounding (0.5 → ไปเลขคู่) — ลด bias สะสม, ใช้ใน accountingDOWN— ตัดเศษทิ้ง (truncate)CEILING/FLOOR— ปัดขึ้น / ปัดลง
💡 เงินใน production: เก็บเป็น
BigDecimal(Java) +DECIMAL(p, s)(DB) — ไม่ใช่double/FLOATสำหรับเงิน:DECIMAL(19,4)เป็น pattern ที่ใช้กันใน enterprise (19 หลัก, 4 ตำแหน่งทศนิยม) — ตรวจ requirement ของระบบก่อนเสมอ
BigInteger — เลขเต็มขนาดไม่จำกัด
ตอนนี้รู้แค่ว่า มี BigInteger ไว้เก็บเลขที่ใหญ่เกิน long ก็พอ — ใช้กับ crypto, combinatorics, big-number math เดี๋ยวเจอจริงค่อยกลับมาดู
ดู API ของ BigInteger (ข้ามรอบแรกได้)
📚 API อ้างอิงสำหรับกลับมาดูทีหลัง — มือใหม่ปิดได้เลย method เช่น
isProbablePrime,shiftLeft,gcdใช้ในงานเฉพาะทาง ไม่ต้องจำตอนนี้
java
// ต้อง import ที่ต้นไฟล์ก่อน public class:
import java.math.BigInteger;
BigInteger a = new BigInteger("123456789012345678901234567890");
BigInteger b = BigInteger.valueOf(10); // จาก long
BigInteger c = a.add(b); // +
BigInteger d = a.multiply(b); // ×
BigInteger e = a.divide(b); // ÷ (integer)
BigInteger[] dr = a.divideAndRemainder(b); // {ผลหาร, เศษ}
BigInteger f = a.pow(20); // ยกกำลัง
BigInteger g = a.mod(BigInteger.valueOf(7)); // mod
BigInteger gcd = a.gcd(b); // gcd
boolean prime = BigInteger.valueOf(97).isProbablePrime(20); // ตรวจสอบว่าเป็นจำนวนเฉพาะหรือไม่
// constants
BigInteger.ZERO; BigInteger.ONE; BigInteger.TWO; BigInteger.TEN;
// shift / bitwise
a.shiftLeft(8); // a << 8
a.and(b); a.or(b); a.xor(b);
// แปลงกลับ
long longValue = a.longValueExact(); // throw ถ้าเกิน range
int intValue = a.intValueExact();💡
BigInteger/BigDecimalimmutable (อิมมิวเทเบิล = เปลี่ยนค่าในตัวเองไม่ได้) — ทุก operation return ตัวใหม่; เร็วกว่า primitive ไม่ได้แน่นอน — ใช้เฉพาะตอนต้องการความแม่นยำ/ขนาด
4.2.1 ⚠️ Integer Cache + autoboxing pitfall
⚠️ ถ้าข้าม section 1.1 (primitive vs reference) มาก่อนหน้านี้ ให้ย้อนกลับไปอ่านก่อน — ไม่งั้นจะงงว่าทำไม
Integerเทียบด้วย==ถึงพังในบางกรณี🟢 ส่วนนี้เป็นความรู้เสริม — ตอนนี้รู้แค่ว่า "เปรียบเทียบ wrapper ด้วย
.equals()ไม่ใช่==" ก็พอ รายละเอียดเดี๋ยวบทที่ 5 (object) และบทที่ 7 จะปูพื้นเพิ่ม
Autoboxing คืออะไร
Autoboxing = Java แปลง primitive (int) เป็น Wrapper class (class ที่ห่อ primitive ให้เป็น object เช่น Integer ห่อ int, Long ห่อ long) ให้อัตโนมัติ และแปลงกลับ (unboxing) ให้ด้วย
Integer Cache คืออะไร
Integer Cache = JVM เก็บ object Integer ของเลขช่วง -128 ถึง 127 ไว้ใช้ซ้ำ ทำให้ Integer a = 100; Integer b = 100; อ้างถึง object เดียวกัน แต่ 200 กลับสร้าง object คนละตัว — เป็นกับดักของ == กับ wrapper
JVM มีระบบ cache (เก็บ object ไว้ใช้ซ้ำ) สำหรับตัวเลขช่วง -128 ถึง 127:
java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true (cached)
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false (object ใหม่!)
System.out.println(c.equals(d)); // true ใช้ equals เสมอกฎ: เปรียบเทียบ
Integer/Long/Doubleฯลฯ → ใช้.equals()หรือ unbox เป็น primitive ก่อนค่อย==
Autoboxing performance hit
java
// ❌ ช้า — Long object ~999,999 ตัว สร้าง/GC ทิ้ง
Long sum = 0L; // wrapper
for (long i = 1; i < 1_000_000; i++) sum += i; // autobox/unbox ทุก iter
// ✅ เร็วกว่า ~10x
long sum2 = 0L; // primitive
for (long i = 1; i < 1_000_000; i++) sum2 += i;ระวัง: Map<String, Integer>, List<Integer> ฯลฯ มี boxing เสมอ — งานคำนวณหนัก ๆ ใช้ int[] หรือ IntStream ดีกว่า
4.3 ตัวอักษรเดี่ยว (char)
| Type | ขนาด | เก็บอะไร |
|---|---|---|
char | 2 byte | ตัวอักษร Unicode ตัวเดียว |
java
char letter = 'A';
char digit = '7';
char thai = 'ก';
char unicode = 'é'; // ตัวอักษร Unicode ที่มี accent — ใส่ตรงได้ถ้า source file เป็น UTF-8
char unicodeEscape = '\u00E9'; // Unicode escape — ได้ค่าเดียวกับ 'é'ใช้ single quote (') สำหรับ char เสมอ — ห้ามใช้ double quote
char จริง ๆ คือ "ตัวเลข 16 bit" ที่ตีความเป็นตัวอักษร:
java
char c = 'A';
int code = c; // 65 (ASCII code ของ 'A')
char nextChar = (char)(c + 1); // 'B'4.4 boolean
| Type | ค่าที่เป็นได้ |
|---|---|
boolean | true หรือ false |
java
boolean isActive = true;
boolean isAdmin = false;
boolean isAdult = age >= 18; // ใช้ผล comparison ก็ได้ไม่เหมือนภาษา C — Java แยก boolean กับ int ขาด:
java
if (1) { ... } // ❌ error: int cannot be converted to boolean
if (x = 5) {...} // ❌ error: assignment returns int, ไม่ใช่ booleanนี่เป็นข้อดี — กัน bug แบบ "ลืม == ใส่ ="
⚠️ gotcha สำหรับคนมาจาก C:
if (x = 5)เมื่อ x เป็นint→ compile error ใน Java ✅ (ป้องกันได้)if (b = true)เมื่อ b เป็นboolean→ compile ผ่านแต่เป็น bug ⚠️ เพราะผล expression เป็นtrueเสมอ — ระวังตอน port code จาก C/C++
5. Reference Type — String
String ไม่ใช่ primitive — เป็น class (object) — แต่ใช้บ่อยจนต้องเรียนก่อน
⚠️ ถ้าข้าม section 1.1 (primitive vs reference) มาก่อนหน้านี้ ให้ย้อนกลับไปอ่านก่อน — ไม่งั้นจะงงว่าทำไม
Stringเทียบด้วย==ถึงพังในบางกรณี (ดู 5.3 ข้างล่าง)
📝 class คืออะไร: แม่แบบของ object — เดี๋ยวบทที่ 5 อธิบาย ตอนนี้รู้แค่ว่า
Stringไม่ใช่ primitive ก็พอ
java
String name = "Anna";
String message = "Hello, World!";
String empty = "";
String nullable = null; // string ที่ไม่มีค่า — ระวังใช้ (ดูคำอธิบายด้านล่าง)📝
nullคืออะไร: ค่า "ว่างเปล่า" หมายถึงตัวแปร reference ยังไม่ชี้ไปที่ object ใด ๆ — ถ้าเรียก method ของตัวแปรที่เป็นnull(เช่นnullable.length()) จะ throwNullPointerException(NPE)
5.1 String concatenation — ต่อ string
ใช้ +:
java
String first = "Hello";
String second = "World";
String result = first + ", " + second + "!"; // "Hello, World!"+ กับ string จะ convert ทุกอย่างเป็น string:
java
int age = 25;
String message = "Age: " + age; // "Age: 25"
double price = 99.99;
String result = "Price: $" + price; // "Price: $99.99"5.2 String method ที่ใช้บ่อย
java
String s = "Hello, World!";
s.length() // 13
s.toUpperCase() // "HELLO, WORLD!"
s.toLowerCase() // "hello, world!"
s.charAt(0) // 'H'
s.indexOf("World") // 7
s.substring(7) // "World!"
s.substring(7, 12) // "World"
s.replace("World", "Java") // "Hello, Java!"
s.contains("World") // true
s.startsWith("Hello") // true
s.endsWith("!") // true
s.trim() // ตัด whitespace หัวท้าย
s.split(", ") // ["Hello", "World!"]
s.isEmpty() // false
s.isBlank() // false (true เมื่อมีแต่ whitespace)5.3 ⚠️ Pitfall: เปรียบเทียบ String ด้วย == ผิด
⚠️ กับดักที่พลาดบ่อยที่สุดของมือใหม่: หลายภาษา (เช่นบางภาษาที่ operator เทียบเนื้อหาให้อัตโนมัติ) ทำให้เข้าใจว่า
==เทียบ "ค่า" — แต่ใน Java==กับStringเทียบ reference (ที่อยู่ใน memory) เสมอ ไม่ใช่เนื้อหา ถ้าใช้นิสัยเดิมมาเขียน Java จะได้บั๊กที่บางทีถูก บางทีผิด (ดูตัวอย่างข้างล่าง) — ใช้.equals()เสมอ
java
String a = "hello";
String b = "hello";
String c = new String("hello");
a == b // true (string pool) — อย่าพึ่งพาสิ่งนี้
a == c // false (แน่ ๆ)
a.equals(c) // trueกฎ: เปรียบเทียบ string ใช้ .equals() เสมอ ไม่ใช่ ==
ทำไม:
==เทียบ reference — "ตัวแปร 2 ตัวชี้ที่ memory เดียวกันมั้ย".equals()เทียบ เนื้อหา — "ข้อความเหมือนกันมั้ย"
(a == b ในตัวอย่างเป็น true เพราะ Java optimize "string pool" — แต่อย่าพึ่ง — ใช้ .equals() เสมอ)
String Pool คืออะไร (เสริม — ข้ามได้)
JVM เก็บ string literal ทั้งหมดไว้ในที่ shared เรียก string pool (อยู่ใน heap ตั้งแต่ Java 7):
java
String a = "hello"; // ใส่ "hello" ใน pool, a ชี้ไปที่นั่น
String b = "hello"; // เจอใน pool แล้ว → b ชี้ตำแหน่งเดิม
String c = new String("hello"); // ❌ บังคับสร้าง object ใหม่นอก pool
System.out.println(a == b); // true (ตำแหน่งเดียวกัน)
System.out.println(a == c); // falseทำไมรู้เรื่องนี้:
- เห็นโค้ดเก่าใช้
==กับ string แล้วงงว่าทำไม "บางทีถูก บางทีผิด" - เข้าใจว่าทำไม
new String("x")แทบไม่มีประโยชน์ในยุคใหม่
💡 ปล่อยให้ JVM จัดการ — เขียน
"hello"ปกติ, เทียบด้วย.equals()พอ
5.3.1 StringBuilder / StringBuffer — string ที่แก้ได้
String เป็น immutable (เปลี่ยนค่าในตัวเองไม่ได้) — ทุกครั้งที่ + คุณสร้าง object ใหม่:
java
// ❌ ช้า — สร้าง String 1000 ตัว แต่ละตัว copy ของเดิมแล้ว append
String s = "";
for (int i = 0; i < 1000; i++) {
s += i; // ช้ามากเมื่อ loop ยาว
}ใช้ StringBuilder แทน — buffer ที่ append/insert/delete ใน-place ได้:
java
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString(); // เร็วกว่ามาก📝 เกร็ด: ยิ่งข้อมูลเยอะ ยิ่งช้าขึ้นเร็วมาก — แบบใช้
+ใน loop จะช้าขึ้นแบบทวีคูณตามจำนวนรอบ (เพราะ copy string เดิมซ้ำทุก iteration) ส่วนแบบStringBuilderช้าขึ้นตามจำนวนข้อมูลตรง ๆ เท่านั้น (ไม่ทวีคูณ) เดี๋ยวบทที่ 7 จะอธิบายเรื่องนี้ด้วยศัพท์ทางการ (Big-O) ตอนนี้รู้แค่ว่าStringBuilderเร็วกว่า+ใน loop มากก็พอ
Method หลัก
java
sb.append("text").append(123).append(' '); // chain ได้
sb.insert(0, "prefix-"); // แทรกตำแหน่ง
sb.delete(5, 10); // ลบช่วง [5,10)
sb.deleteCharAt(0);
sb.replace(0, 3, "new");
sb.reverse();
sb.setLength(0); // เคลียร์
int len = sb.length();
char c = sb.charAt(0);
sb.setCharAt(0, 'A');
String out = sb.toString();StringBuilder vs StringBuffer
| StringBuilder | StringBuffer | |
|---|---|---|
| Thread-safe? | ❌ ไม่ | ✅ ปลอดภัยหลาย thread แต่ช้ากว่า |
| ความเร็ว | เร็วกว่า | ช้ากว่า (synchronized overhead) |
| เริ่มมีเมื่อ | Java 5 | Java 1.0 |
📝 Thread-safe = ปลอดภัยเวลามีหลายงานทำพร้อมกัน (เดี๋ยวเรียนบทที่ 12) แต่ช้ากว่า
กฎ: ใช้ StringBuilder เป็น default; StringBuffer เฉพาะ "string ที่ thread หลายตัวแก้" ซึ่งหายากมาก
⚠️ แม้
StringBufferแต่ละ method จะ synchronized แต่การ append หลายครั้งเป็นชุดยังไม่ atomic — ถ้าต้องการ thread-safe string building จริง ๆ ควรใช้ localStringBuilderต่อ thread หรือใช้ synchronization ระดับสูงกว่า (เรียนบทที่ 12)
💡 ข่าวดี: ตั้งแต่ Java 9+ — compiler ฉลาดขึ้น ใช้
+ต่อ String ใน expression เดียว (เช่น"a" + b + c) ก็เร็วพอ ไม่ต้องStringBuilderใช้StringBuilderตอนจำเป็นจริง ๆ คือ: loop หรือ ต่อแบบ conditionalJVM trivia (ข้ามได้): ภายในใช้อะไร
compiler แปลง
+ของ String อัตโนมัติเป็นStringConcatFactory(invokedynamic = คำสั่ง bytecode ที่ตัดสินใจว่าจะเรียกโค้ดตัวไหนตอนรันจริง) → fast path ในกรณีง่าย ๆ ("fast path" = เส้นทางทำงานที่เร็วเป็นพิเศษ ข้ามขั้นตอนที่ไม่จำเป็น)
5.4 String formatting
แบบเก่า (printf style):
java
String name = "Anna";
int age = 25;
String message = String.format("Name: %s, Age: %d", name, age);
// "Name: Anna, Age: 25"แบบใหม่ (Java 15+, Text Block + formatted):
java
// Text Block = string หลายบรรทัด เขียนด้วย """ เปิด-ปิด — รายละเอียดเรียนภายหลัง
String message = """
Name: %s
Age: %d
""".formatted(name, age);
// หมายเหตุ: ตำแหน่ง indent ของ closing """ กำหนดว่าจะตัด leading space กี่ช่อง
// และผลลัพธ์จะมี trailing newline ต่อท้ายเสมอ (เพราะ """ อยู่บรรทัดถัดไป)⚠️ String Templates ถูกถอนออกแล้ว — feature นี้ (เคยเขียนแบบ
STR."Name: \{name}") เป็น preview ใน Java 21/22/23 แต่ถูก withdrawn ออกทั้งหมด และยังไม่มีใน Java 25 — ยังไม่มีกำหนด (กำลังออกแบบใหม่) ว่าจะกลับมาเมื่อไหร่ ปัจจุบัน (2026) ให้ใช้String.format(...)หรือ"...".formatted(...)เท่านั้น
6. Operator
6.1 Arithmetic (คณิตศาสตร์)
| Operator | ความหมาย | ตัวอย่าง |
|---|---|---|
+ | บวก / ต่อ string | 5 + 3 = 8 |
- | ลบ | 5 - 3 = 2 |
* | คูณ | 5 * 3 = 15 |
/ | หาร | 10 / 3 = 3 (integer!) |
% | mod (เศษ) | 10 % 3 = 1 |
⚠️ Pitfall: Integer division ตัดเศษทิ้ง
java
int a = 10 / 3; // 3 (ไม่ใช่ 3.33!)
int b = 1 / 2; // 0 (ไม่ใช่ 0.5!)ถ้าต้องการทศนิยม → cast เป็น double อย่างน้อยตัวหนึ่ง:
java
double a = 10.0 / 3; // 3.333... (10.0 เป็น double แล้ว)
double b = (double) 10 / 3; // 3.333... (cast 10 เป็น double)
double c = 10 / 3.0; // 3.333...
double d = 10 / 3; // ได้ 3.0 ไม่ใช่ 3.333 — เพราะ 10/3 คำนวณเป็น int (=3) ก่อน แล้วค่อย assign เป็น double6.2 Assignment
| Operator | ความหมาย |
|---|---|
= | กำหนดค่า |
+= | บวกแล้วเก็บ (x += 5 เท่ากับ x = x + 5) |
-= | ลบแล้วเก็บ |
*= | คูณแล้วเก็บ |
/= | หารแล้วเก็บ |
%= | mod แล้วเก็บ |
java
int x = 10;
x += 5; // x = 15
x -= 3; // x = 12
x *= 2; // x = 24
x /= 4; // x = 6
x %= 4; // x = 26.3 Increment / Decrement
java
int x = 5;
x++; // x = 6 (เพิ่มทีละ 1)
x--; // x = 5 (ลดทีละ 1)⚠️ Pre-increment vs Post-increment
java
int x = 5;
int a = x++; // a = 5, x = 6 (post: ใช้ค่าก่อน, เพิ่มทีหลัง)
int y = 5;
int b = ++y; // b = 6, y = 6 (pre: เพิ่มก่อน, ใช้ค่าทีหลัง)เคล็ดจำ: ++ อยู่หลัง = "ใช้ค่าเก่าก่อน"; ++ อยู่หน้า = "ใช้ค่าใหม่"
ใช้แค่ตอน loop:
java
for (int i = 0; i < 10; i++) { ... }ในกรณีอื่น เขียน x = x + 1; หรือ x += 1; ดีกว่า อ่านง่ายและไม่งง
6.4 Comparison (เปรียบเทียบ — return boolean)
| Operator | ความหมาย |
|---|---|
== | เท่ากัน |
!= | ไม่เท่ากัน |
< | น้อยกว่า |
> | มากกว่า |
<= | น้อยกว่าหรือเท่ากับ |
>= | มากกว่าหรือเท่ากับ |
java
int age = 25;
age == 25 // true
age != 30 // true
age >= 18 // true
age < 18 // false⚠️ อย่าใช้ == กับ String — ใช้ .equals()
6.5 Logical (boolean)
| Operator | ความหมาย |
|---|---|
&& | AND (ทั้งสองต้อง true) |
|| | OR (อย่างน้อยตัวหนึ่ง true) |
! | NOT (สลับ true/false) |
java
int age = 25;
boolean isAdult = age >= 18 && age < 60; // true
boolean isYoungOrOld = age < 18 || age > 60; // false
boolean isMinor = !(age >= 18); // falseShort-circuit evaluation
&& กับ || หยุดประเมินทันทีที่รู้ผล:
java
// ถ้าตัวแรก false, ตัวที่สองไม่ถูก evaluate เลย
if (user != null && user.isActive()) {
// safe — ถ้า user == null จะไม่เรียก user.isActive() (NullPointerException)
}
// ถ้าตัวแรก true, ตัวที่สองไม่ถูก evaluate
if (cache.has(key) || database.fetch(key) != null) {
// ถ้า cache มี — ไม่ต้องเสียเวลาเรียก database
}นี่คือเหตุผลที่ลำดับ condition สำคัญ:
java
if (user.isActive() && user != null) { ... } // ❌ ถ้า user == null จะ error ก่อน
if (user != null && user.isActive()) { ... } // ✅ check null ก่อน6.6 Ternary operator (?😃
shortcut ของ if-else:
java
// แบบเดิม
String label;
if (age >= 18) {
label = "Adult";
} else {
label = "Minor";
}
// แบบ ternary
String message = (age >= 18) ? "Adult" : "Minor";อ่านว่า: "ถ้า age >= 18 ใช้ "Adult" ถ้าไม่ใช่ใช้ "Minor""
ใช้เมื่อ logic ง่ายมาก — ถ้าซับซ้อนใช้ if-else เถอะ อ่านง่ายกว่า
7. Type Conversion — แปลง type
7.1 Implicit (อัตโนมัติ) — เล็ก → ใหญ่
java
int x = 100;
long y = x; // ✅ OK — int ใส่ใน long ได้ (long ใหญ่กว่า)
double z = x; // ✅ OK — int → double ได้ลำดับ widening conversion: byte → short → int → long → float → double
⚠️ หมายเหตุ: widening conversion บางคู่ อาจสูญเสีย precision แม้ไม่ใช่ error:
int → float—floatมีความแม่นยำ ~7 หลัก ค่าintที่มากกว่า 2²⁴ (≈16 ล้าน) จะเริ่มเสียความแม่นยำlong → float—floatมีแค่ ~7 หลัก ขณะที่longมีสูงสุด 19 หลักlong → double—doubleมีความแม่นยำ ~15 หลัก ค่าlongที่มากกว่า 2⁵³ (≈9 พันล้านล้าน) จะเริ่มเสียความแม่นยำถ้าต้องการความแม่นยำกับตัวเลขใหญ่ให้ใช้
BigDecimalแทน
7.2 Explicit (cast) — ใหญ่ → เล็ก
java
double d = 3.7;
int i = d; // ❌ error: lossy conversion
int i = (int) d; // ✅ i = 3 (ตัดทศนิยมทิ้ง — ไม่ปัดเศษ)⚠️ Cast = บอก compiler ว่า "ฉันรับผิดชอบเอง" — อาจเสียข้อมูล
java
int huge = 300;
byte b = (byte) huge; // b = 44 (overflow)7.3 String ↔ Number
Number → String
java
int x = 42;
String s = Integer.toString(x); // "42"
String s2 = String.valueOf(x); // "42"
String s3 = "" + x; // "42" (เขียนสั้น แต่ไม่เป็น idiomatic Java — String.valueOf(x) อ่านชัดกว่าและ performance เทียบเท่า)String → Number
java
String s = "42";
int x = Integer.parseInt(s); // 42
double d = Double.parseDouble("3.14"); // 3.14
long l = Long.parseLong("1000000000000");⚠️ ถ้า string ไม่ถูก format จะ throw NumberFormatException:
java
int x = Integer.parseInt("abc"); // ❌ NumberFormatException8. Final variable — ตัวแปรที่เปลี่ยนไม่ได้
ใส่ keyword final ห้าม assign ค่าใหม่:
java
final int MAX_USERS = 100;
MAX_USERS = 200; // ❌ error: cannot assign a value to final variableใช้สำหรับ:
- ค่าคงที่ —
final double PI = 3.14159; - ค่าที่ตั้งแค่ครั้งเดียวแล้ว lock —
final String userId = generateId(); - บังคับ immutability — บอก reader ว่า "ไม่เปลี่ยนแน่นอน"
Convention: ค่าคงที่ (final + static) ใช้ UPPER_SNAKE_CASE:
java
public static final int MAX_LOGIN_ATTEMPTS = 5;
public static final String API_BASE_URL = "https://api.example.com";9. Constant vs Variable
Java มีระดับของ "ความเปลี่ยนแปลงได้" 3 แบบ — ตัวแปรปกติ (แก้ได้), final (กำหนดค่าได้ครั้งเดียว), และ static final (ค่าคงที่ระดับ class) ตารางนี้เทียบให้เห็นความต่างทั้ง syntax, การแก้ค่า และธรรมเนียมการตั้งชื่อ:
| Variable (ตัวแปรปกติ) | Final variable (กำหนดค่าได้ครั้งเดียว) | Static final (ค่าคงที่ระดับ class) | |
|---|---|---|---|
| Syntax | int x = 5; | final int x = 5; | static final int X = 5; |
| เปลี่ยนค่าได้ | ✅ | ❌ | ❌ |
| Convention name | camelCase | camelCase | UPPER_SNAKE_CASE |
| ที่อยู่ | local/field | local/field | class-level |
📝 ข้อควรระวังตอนทำ library: ถ้า
static finalเก็บค่าคงที่แบบที่ compiler รู้ค่าตั้งแต่ compile time (เช่นstatic final int X = 5;) — javac จะ inline ค่านั้นเข้าไปใน bytecode ของโค้ดที่เรียกใช้เลย ไม่ใช่แค่ชี้ไปอ่านค่าจาก class ตอน runtime ผลคือถ้าเปลี่ยนค่าคงที่นี้ใน library แล้วแจก .jar ใหม่ โค้ดที่เรียกใช้ (caller) ต้อง compile ใหม่ ไม่ใช่แค่เปลี่ยนไฟล์ .jar เฉย ๆ — ไม่งั้นจะยังใช้ค่าเก่าที่ inline ไว้อยู่
10. Scope — ที่อยู่ของตัวแปร
ตัวแปร "มีชีวิต" เฉพาะใน block ที่ประกาศ:
java
public static void main(String[] args) {
int x = 5;
if (x > 0) {
int y = 10; // y มีชีวิตเฉพาะใน if-block
System.out.println(y); // ✅
}
System.out.println(y); // ❌ error: cannot find symbol y
}กฎ: ตัวแปรอยู่ได้ตั้งแต่ประกาศจนถึง } ที่ปิด block นั้น
ห้ามประกาศซ้ำใน scope เดียวกัน:
java
int x = 5;
int x = 10; // ❌ error: variable x is already defined⚠️ Java ไม่อนุญาต ให้ local variable ซ้อนกันใน method เดียวกัน — แม้จะอยู่ใน block ลึกกว่าก็ตาม:
java
int x = 5;
if (true) {
int x = 10; // ❌ compile error: variable x is already defined in method main(String[])
}shadowing ทำได้เฉพาะ field ระดับ class กับ local variable ใน method (ต่างกันคนละระดับ):
java
class Example {
int x = 5; // field
void method() {
int x = 10; // ✅ local variable ซ่อน field — แต่ confusing!
System.out.println(x); // 10 (local)
System.out.println(this.x); // 5 (field)
}
}💡 อย่า shadow ตัวแปร — โค้ดจะอ่านยาก หา bug ยาก
11. Checkpoint รวม
🛠️ Checkpoint 2.1 — ทำความเข้าใจ type
ลองคิดดูว่าโค้ดต่อไปนี้ print ค่าอะไร — แล้ว run เช็ค
java
public class Main {
public static void main(String[] args) {
int a = 7;
int b = 2;
System.out.println(a / b);
System.out.println((double) a / b);
System.out.println(a % b);
double x = 0.1 + 0.2;
System.out.println(x == 0.3);
String s = "5";
int n = Integer.parseInt(s) + 10;
System.out.println(n);
}
}📖 เฉลย
text
3 (integer division)
3.5 (double division)
1 (7 mod 2)
false (floating-point ไม่แม่นยำ)
15 (5 + 10)🛠️ Checkpoint 2.2 — เครื่องคิดเลข
เขียนโปรแกรมรับเลข 2 ตัวจาก args[0] กับ args[1] (ส่งมาเป็น String, ต้อง parse) แล้วพิมพ์ผล +, -, *, /, %
ทดสอบ: java Main 10 3 ควรพิมพ์:
text
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3
10 % 3 = 1📖 เฉลย
java
public class Main {
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println(a + " + " + b + " = " + (a + b));
System.out.println(a + " - " + b + " = " + (a - b));
System.out.println(a + " * " + b + " = " + (a * b));
System.out.println(a + " / " + b + " = " + (a / b));
System.out.println(a + " % " + b + " = " + (a % b));
}
}⚠️ ระวัง: a + " + " + b + " = " + (a + b) — วงเล็บรอบ a + b สำคัญมาก ถ้าไม่มีวงเล็บ Java จะอ่านจากซ้าย: "... = " + a + b = "... = 103" (ต่อ string) มีวงเล็บแล้ว → คำนวณก่อน → "... = 13"
🛠️ Checkpoint 2.3 — อายุเป็นวัน
รับอายุ (ปี) จาก args[0] แล้วพิมพ์เป็นวัน (สมมติ 1 ปี = 365 วัน)
ทดสอบ: java Main 25 → 25 years = 9125 days
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
int years = Integer.parseInt(args[0]);
int days = years * 365;
System.out.println(years + " years = " + days + " days");
}
}🛠️ Checkpoint 2.4 — ตรวจ Adult/Minor
รับอายุจาก args[0] แล้วใช้ ternary operator พิมพ์ "Adult" หรือ "Minor"
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
int age = Integer.parseInt(args[0]);
String status = (age >= 18) ? "Adult" : "Minor";
System.out.println(status);
}
}🛠️ Checkpoint 2.5 (ท้าทาย) — เงินทอน
มี 1000 บาท ต้องการซื้อของราคา args[0] จำนวน args[1] ชิ้น พิมพ์: ราคารวม, เงินทอน, ถ้าเงินไม่พอให้พิมพ์ "เงินไม่พอ"
💡 ใบ้: ใช้
if (change >= 0) { ... } else { ... }— รูปแบบเดียวกับ Checkpoint 1.4 (เรียนละเอียดบทที่ 3)
📖 เฉลย
java
public class Main {
public static void main(String[] args) {
// ตัวอย่างนี้ใช้ double เพื่อความง่ายในบทนี้ — งาน production ที่เกี่ยวกับเงินให้ใช้ BigDecimal เสมอ ดู section 4.2
double price = Double.parseDouble(args[0]);
int quantity = Integer.parseInt(args[1]);
double money = 1000.0;
double total = price * quantity;
double change = money - total;
System.out.println("ราคารวม: " + total);
if (change >= 0) {
System.out.println("เงินทอน: " + change);
} else {
System.out.println("เงินไม่พอ ขาดอีก: " + (-change));
}
}
}(if-else เรียนละเอียดในบทถัดไป — ตรงนี้ใช้แค่ตัวอย่าง)
12. สรุปสิ่งที่ได้จากบทนี้
✅ ตัวแปรคือ "ป้ายชื่อ" ที่ผูกกับ memory location ✅ ประกาศตัวแปรด้วย type name = value; หรือใช้ var ให้ compiler เดา type ✅ Primitive 8 ตัว: byte, short, int, long, float, double, char, boolean ✅ String เป็น reference type — ใช้ .equals() เทียบ ไม่ใช่ == ✅ Operator: arithmetic, assignment, comparison, logical, ternary ✅ Integer division ตัดเศษ, floating-point ไม่แม่นยำ → ใช้ BigDecimal กับเงิน ✅ Type conversion: implicit (เล็ก→ใหญ่) vs explicit cast (ใหญ่→เล็ก, อาจเสียข้อมูล) ✅ final ห้ามเปลี่ยนค่า → ใช้กับค่าคงที่ (MAX_USERS) ✅ Scope: ตัวแปรอยู่เฉพาะใน block ที่ประกาศ