Skip to content

บทที่ 2 — ตัวแปร, ประเภทข้อมูล, และ Operator

← บทที่ 1 | สารบัญ | บทที่ 3: Control Flow →

บทนี้คือ พื้นฐานของพื้นฐาน — ทุกโปรแกรมไม่ว่าใหญ่แค่ไหนก็เริ่มจาก "เก็บข้อมูล" และ "คำนวณ"

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

  • เข้าใจว่า "ตัวแปร" คืออะไรจริง ๆ (ไม่ใช่แค่ "กล่อง")
  • รู้จัก 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 initialized

Java ระวังเรื่องนี้มาก — บังคับเรา 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 — ageAge

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ขนาดช่วงเมื่อไหร่ใช้
byte1 byte (8 bit)-128 ถึง 127binary data, network protocol
short2 byte-32,768 ถึง 32,767ไม่ค่อยใช้
int4 byte±2.1 พันล้านdefault สำหรับจำนวนเต็ม
long8 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 ว่าเป็น long

Number 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 (ความแม่นยำ)เมื่อไหร่ใช้
float4 byte~7 หลักประหยัด memory, graphics
double8 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 / true

RoundingMode ที่ใช้บ่อย:

  • HALF_UP — ปกติ (0.5 → 1) — default ของวิชาคณิตศาสตร์
  • HALF_EVEN — banker's rounding (0.5 → ไปเลขคู่) — ลด bias สะสม, ใช้ใน accounting
  • DOWN — ตัดเศษทิ้ง (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/BigDecimal immutable (อิมมิวเทเบิล = เปลี่ยนค่าในตัวเองไม่ได้) — ทุก 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ขนาดเก็บอะไร
char2 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ค่าที่เป็นได้
booleantrue หรือ 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 เป็น intcompile error ใน Java ✅ (ป้องกันได้)
  • if (b = true) เมื่อ b เป็น booleancompile ผ่านแต่เป็น 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()) จะ throw NullPointerException (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

StringBuilderStringBuffer
Thread-safe?❌ ไม่✅ ปลอดภัยหลาย thread แต่ช้ากว่า
ความเร็วเร็วกว่าช้ากว่า (synchronized overhead)
เริ่มมีเมื่อJava 5Java 1.0

📝 Thread-safe = ปลอดภัยเวลามีหลายงานทำพร้อมกัน (เดี๋ยวเรียนบทที่ 12) แต่ช้ากว่า

กฎ: ใช้ StringBuilder เป็น default; StringBuffer เฉพาะ "string ที่ thread หลายตัวแก้" ซึ่งหายากมาก

⚠️ แม้ StringBuffer แต่ละ method จะ synchronized แต่การ append หลายครั้งเป็นชุดยังไม่ atomic — ถ้าต้องการ thread-safe string building จริง ๆ ควรใช้ local StringBuilder ต่อ thread หรือใช้ synchronization ระดับสูงกว่า (เรียนบทที่ 12)

💡 ข่าวดี: ตั้งแต่ Java 9+ — compiler ฉลาดขึ้น ใช้ + ต่อ String ใน expression เดียว (เช่น "a" + b + c) ก็เร็วพอ ไม่ต้อง StringBuilder ใช้ StringBuilder ตอนจำเป็นจริง ๆ คือ: loop หรือ ต่อแบบ conditional

JVM 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ความหมายตัวอย่าง
+บวก / ต่อ string5 + 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 เป็น double

6.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 = 2

6.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);               // false

Short-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 → floatfloat มีความแม่นยำ ~7 หลัก ค่า int ที่มากกว่า 2²⁴ (≈16 ล้าน) จะเริ่มเสียความแม่นยำ
  • long → floatfloat มีแค่ ~7 หลัก ขณะที่ long มีสูงสุด 19 หลัก
  • long → doubledouble มีความแม่นยำ ~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");     // ❌ NumberFormatException

8. 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)
Syntaxint x = 5;final int x = 5;static final int X = 5;
เปลี่ยนค่าได้
Convention namecamelCasecamelCaseUPPER_SNAKE_CASE
ที่อยู่local/fieldlocal/fieldclass-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 2525 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, booleanString เป็น 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 ที่ประกาศ


→ ไปบทที่ 3: Control Flow