Scope, Lifetime, Reference Memory

บทนำ: ที่มาของตัวแปร

จนถึงตอนนี้ เราประกาศตัวแปร (attributes, local variables) แต่ไม่เคยคิดว่า:

  1. ตัวแปรนี้ เข้าถึงได้จากที่ไหนบ้าง? (Scope)
  2. ตัวแปรนี้ “มีชีวิต” นานแค่ไหน? (Lifetime)
  3. ตัวแปรนี้เก็บอยู่ที่ไหนในหน่วยความจำ? (Reference vs Value)

Scope: ขอบเขตการเข้าถึง

Scope คืออะไร?

Scope (ขอบเขต) คือ ส่วนของโปรแกรมที่สามารถเข้าถึงตัวแปรได้

ตัวแปรที่ประกาศในที่หนึ่ง อาจเข้าถึงไม่ได้ ในที่อื่น


ประเภท Scope

1. Class Scope (Member Variables)

Attributes ที่ประกาศใน class สามารถเข้าถึงได้ทั่วทั้ง class

javapublic class Student {
    // Class scope - เข้าถึงได้จากทุก methods ใน class นี้
    private String name;
    private double gpa;
    
    public void study() {
        // เข้าถึงได้: this.name, this.gpa
        System.out.println(this.name + " is studying");
    }
    
    public void displayInfo() {
        // เข้าถึงได้: this.name, this.gpa
        System.out.println("Name: " + this.name);
    }
}

Visual:

textClass Student
├─ name (private) ─┐
├─ gpa (private)  ─┤ Accessible everywhere in class
├─ study()        ─┤ Can access name and gpa
├─ displayInfo() ──┘
└─ ...

2. Method Scope (Local Variables)

ตัวแปรที่ประกาศใน method เข้าถึงได้เฉพาะใน method นั้นเท่านั้น

javapublic class Calculator {
    public void calculate() {
        // Local variable - เข้าถึงได้เฉพาะใน calculate() เท่านั้น
        int result = 10 + 5;
        System.out.println(result);
    }
    
    public void anotherMethod() {
        // ❌ ไม่สามารถเข้าถึง result ได้
        // System.out.println(result);  // ERROR!
    }
}

Visual:

textmethod1() {
    int x = 5;  ← เข้าถึงได้เฉพาะ method1
    // x accessible here
}

method2() {
    // ❌ x not accessible here
    // System.out.println(x);  // ERROR!
}

3. Block Scope (Control Structures)

ตัวแปรใน { } scope เข้าถึงได้เฉพาะใน block นั้น

javapublic void demo() {
    int x = 10;  // Method scope
    
    if (x > 5) {
        int y = 20;  // Block scope - เข้าถึงได้เฉพาะใน if block
        System.out.println(x);  // ✓ OK (x is in method scope)
        System.out.println(y);  // ✓ OK (y is in if block)
    }
    
    System.out.println(x);      // ✓ OK
    System.out.println(y);      // ❌ ERROR! (y not in scope)
}

Visual:

textmethod {
    int x = 10;
    
    if (...) {
        int y = 20;  ← เข้าถึงได้เฉพาะที่นี่
        println(x);  ✓
        println(y);  ✓
    }
    
    println(x);      ✓
    println(y);      ❌ ERROR!
}

ตัวอย่าง: Scope ที่ซับซ้อน

javapublic class ScopeDemo {
    // CLASS SCOPE
    private String className = "ScopeDemo";
    
    public void method1() {
        // METHOD SCOPE
        int localVar1 = 10;
        
        for (int i = 0; i < 3; i++) {
            // BLOCK SCOPE
            int blockVar = i * 2;
            
            System.out.println("className: " + className);     // ✓ class scope
            System.out.println("localVar1: " + localVar1);     // ✓ method scope
            System.out.println("i: " + i);                     // ✓ block scope
            System.out.println("blockVar: " + blockVar);       // ✓ block scope
        }
        
        // ❌ i ไม่เข้าถึงได้ (loop scope)
        // System.out.println(i);  // ERROR!
        
        // ❌ blockVar ไม่เข้าถึงได้ (for block scope)
        // System.out.println(blockVar);  // ERROR!
    }
    
    public void method2() {
        // localVar1 ไม่เข้าถึงได้ (different method scope)
        // System.out.println(localVar1);  // ERROR!
    }
}

Access Modifiers และ Scope

javapublic class AccessDemo {
    public String publicAttribute;           // เข้าถึงได้ทุกที่
    private String privateAttribute;        // เข้าถึงได้เฉพาะใน class นี้
    protected String protectedAttribute;    // เข้าถึงได้ใน subclass
    String defaultAttribute;                // เข้าถึงได้ในแพ็คเกจเดียวกัน
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        AccessDemo demo = new AccessDemo();
        
        demo.publicAttribute = "OK";        // ✓ OK
        demo.privateAttribute = "ERROR";    // ❌ ERROR! (private)
        demo.protectedAttribute = "...";    // ✓ OK (in same package)
        demo.defaultAttribute = "OK";       // ✓ OK (in same package)
    }
}

Lifetime: ช่วงชีวิตของตัวแปร

Lifetime คืออะไร?

Lifetime (ช่วงชีวิต) คือ ช่วงเวลา ที่ตัวแปร มีอยู่ในหน่วยความจำ

ตัวแปรถูกสร้าง (allocated) และถูกลบ (deallocated) ตามขอบเขต


ประเภท Lifetime

1. Instance Variable Lifetime

Instance variables มีชีวิต ตราบเท่า object ยังอยู่ในหน่วยความจำ

javapublic class Student {
    private String name;    // Instance variable
    private double gpa;
    
    public Student(String name, double gpa) {
        this.name = name;   // allocated
        this.gpa = gpa;
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        Student student = new Student("John", 3.75);
        // ที่นี่ student object มีชีวิต
        // name, gpa มีชีวิต
        
        student = null;  // หลังจากนี้ object จะถูกลบ
        // Garbage Collector จะลบ student object
        // name, gpa ก็ถูกลบไปด้วย
    }
}

Timeline:

textmain() เริ่ม
   ↓
new Student() ← object สร้าง
   ├─ name allocated
   ├─ gpa allocated
   ↓
(ใช้งาน object)
   ↓
student = null
   ↓
Garbage Collector
   ├─ name deallocated
   ├─ gpa deallocated
   ├─ object deleted
   ↓
main() จบ

2. Local Variable Lifetime

Local variables มีชีวิต เฉพาะเมื่อ method/block ทำงาน

javapublic void example() {
    int x = 10;  // ← allocated เมื่อ method เริ่มทำงาน
    
    // x สามารถใช้ได้
    System.out.println(x);
    
}  // ← deallocated เมื่อ method จบ

// หลังจากนี้ x ไม่มีชีวิตแล้ว

Timeline:

textexample() เริ่ม
   ↓
int x = 10; ← x allocated
   ↓
(ใช้ x)
   ↓
} ← method จบ
   ↓
x deallocated

3. Static Variable Lifetime

Static variables มีชีวิต นานเท่าโปรแกรมทำงาน

javapublic class Counter {
    public static int count = 0;  // Static variable
    
    public static void increment() {
        count++;
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        Counter.increment();  // count = 1
        // count ยังมีอยู่
        
        Counter.increment();  // count = 2
        // count ยังมีอยู่
        
    }  // main จบ แต่ count ยังมีชีวิต
    
}  // โปรแกรมจบ → count deallocated

Timeline:

textProgram starts
   ↓
Class Counter loaded
   ↓
count allocated ← allocated ทันที
   ↓
(ใช้ count ตลอดโปรแกรม)
   ↓
Program ends
   ↓
count deallocated ← deallocated ตอนท้ายสุด

ตัวอย่าง: Lifetime ทั้งหมด

javapublic class LifetimeDemo {
    // STATIC - มีชีวิตนานเท่าโปรแกรม
    public static int globalCounter = 0;
    
    // INSTANCE - มีชีวิตเท่าที่ object มีอยู่
    private int instanceCounter = 0;
    
    public void method() {
        // LOCAL - มีชีวิตเฉพาะใน method นี้
        int localCounter = 0;
        
        for (int i = 0; i < 3; i++) {
            // BLOCK - มีชีวิตเฉพาะใน for block
            int blockCounter = 0;
        }
        // blockCounter ไม่มีชีวิตแล้ว
    }
    // localCounter ไม่มีชีวิตแล้ว
}

// ใช้งาน
LifetimeDemo demo = new LifetimeDemo();
// demo, instanceCounter เกิด

LifetimeDemo demo2 = new LifetimeDemo();
// demo2, instanceCounter (ของ demo2) เกิด

demo = null;
// demo ลบ, instanceCounter (ของ demo) ลบ

// ยังมี: globalCounter, demo2, instanceCounter (ของ demo2)

demo2 = null;
// demo2 ลบ, instanceCounter (ของ demo2) ลบ

// ยังมี: globalCounter

// Program ends
// globalCounter ลบ

Reference Memory: การเก็บข้อมูลในหน่วยความจำ

Primitive vs Reference Types

Java มีสองวิธีในการเก็บข้อมูล:

ประเภทสิ่งที่เก็บตัวอย่าง
Primitiveค่าจริงint, double, boolean
Referenceที่อยู่ (pointer)String, Object, Array

Primitive Type: เก็บค่าจริง

javaint x = 5;       // เก็บค่า 5 ตรงๆ
double y = 3.14; // เก็บค่า 3.14 ตรงๆ

// Memory:
// x:  [5]
// y:  [3.14]

ทำสำเนา:

javaint a = 10;
int b = a;   // b ก็ได้ 10 (สำเนาค่า)

a = 20;      // a เปลี่ยน
// b ยังคง 10 (ไม่ได้รับผลกระทบ)

Reference Type: เก็บที่อยู่

javaString name = "John";
// Memory:
// name: [memory address 0x123]
//       ↓
//       [0x123] = "John"

// name เก็บ "ที่อยู่" ไม่ใช่ค่าตัวจริง

เมื่อทำสำเนา:

javaString name1 = "John";
String name2 = name1;  // name2 ชี้ไปที่ที่อยู่เดียวกับ name1

// Memory:
// name1: [address 0x123] ──┐
// name2: [address 0x123] ──┤─→ "John"
//                           │

// ทั้ง name1 และ name2 ชี้ไปเดียวกัน!

Stack vs Heap

Java จัดเก็บข้อมูลในสองที่:

StackHeap
เก็บ Primitive valuesเก็บ Objects
เก็บ References(ค่าจริงของ objects)
เล็กกว่าใหญ่กว่า
เร็วช้ากว่า

Visual:

textStack (เร็ว, เล็ก)
┌─────────────┐
│ int x = 5   │
│ double y = 3.14 │
│ name: [ref] │──────┐
│ car: [ref]  │──┐   │
└─────────────┘  │   │
                 │   │
Heap (ช้า, ใหญ่) │   │
┌─────────────┐  │   │
│ Student obj │←─┤   │
│ String "John"│←─────┘
│ Car obj     │
└─────────────┘

ตัวอย่าง: Reference Behavior

javapublic class ReferenceBehavior {
    public static void main(String[] args) {
        // === PRIMITIVE: Copy Value ===
        int a = 10;
        int b = a;
        
        a = 20;
        System.out.println("a = " + a);  // 20
        System.out.println("b = " + b);  // 10 (ไม่เปลี่ยน)
        
        // === REFERENCE: Copy Address ===
        Student student1 = new Student("John", 3.75);
        Student student2 = student1;  // ชี้ไปที่ object เดียวกัน
        
        student1.setGPA(3.85);  // เปลี่ยน student1
        
        System.out.println("student1 GPA: " + student1.getGPA());  // 3.85
        System.out.println("student2 GPA: " + student2.getGPA());  // 3.85 (เปลี่ยน!)
        // ทั้ง student1 และ student2 ชี้ไปเดียวกัน!
        
        // === NULL REFERENCE ===
        Student student3 = null;  // ไม่ชี้ไปไหน
        // student3.displayInfo();  // ERROR! NullPointerException
    }
}

Memory Visualization:

textInitial:
Stack:
┌──────────────────┐
│ a: [10]          │
│ b: [10]          │
│ student1:[ref]   │──┐
│ student2:[ref]   │──┼─→ Student object (John, 3.75)
│ student3:[null]  │  │
└──────────────────┘  └──→ Heap

After: a = 20, student1.setGPA(3.85)
Stack:
┌──────────────────┐
│ a: [20]          │
│ b: [10]          │ ← ต่างกัน (primitive)
│ student1:[ref]   │──┐
│ student2:[ref]   │──┼─→ Student object (John, 3.85)
│ student3:[null]  │  │
└──────────────────┘  └──→ Heap

ทั้ง student1 และ student2 ชี้ไปเดียวกัน

ตัวอย่างที่ 1: Scope Demo

javapublic class ScopeExample {
    // CLASS SCOPE
    private String className = "ScopeExample";
    private static int staticCount = 0;
    
    public void exampleMethod() {
        // METHOD SCOPE
        String methodVar = "Method";
        staticCount++;
        
        // เข้าถึงได้ทั้ง class scope และ method scope
        System.out.println("Class: " + className);
        System.out.println("Method: " + methodVar);
        System.out.println("Static: " + staticCount);
        
        // If block
        if (staticCount > 0) {
            // BLOCK SCOPE
            String ifVar = "If Block";
            System.out.println("If: " + ifVar);
        }
        
        // ❌ ifVar ไม่เข้าถึงได้
        // System.out.println(ifVar);  // ERROR!
        
        // For loop
        for (int i = 0; i < 2; i++) {
            // LOOP BLOCK SCOPE
            String loopVar = "Loop " + i;
            System.out.println(loopVar);
        }
        
        // ❌ i ไม่เข้าถึงได้
        // System.out.println(i);  // ERROR!
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        ScopeExample example = new ScopeExample();
        
        example.exampleMethod();
        example.exampleMethod();
        
        // ❌ methodVar ไม่เข้าถึงได้
        // System.out.println(example.methodVar);  // ERROR!
        
        // ✓ staticCount เข้าถึงได้
        System.out.println("Total calls: " + ScopeExample.staticCount);  // 2
    }
}

ตัวอย่างที่ 2: Lifetime Demo

javapublic class LifetimeExample {
    // STATIC - มีชีวิต: program start → program end
    private static int staticVar = 0;
    
    // INSTANCE - มีชีวิต: object create → object deleted
    private int instanceVar = 0;
    
    public LifetimeExample(int id) {
        instanceVar = id;
        staticVar++;
        System.out.println("Created object " + id + 
                         ", total: " + staticVar);
    }
    
    public void demonstrateLocal() {
        // LOCAL - มีชีวิต: method start → method end
        int localVar = 999;
        System.out.println("LocalVar: " + localVar);
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        System.out.println("=== Create Object 1 ===");
        LifetimeExample obj1 = new LifetimeExample(1);
        // obj1 alive
        // instanceVar (of obj1) alive
        // staticVar = 1
        
        System.out.println("\n=== Call Method ===");
        obj1.demonstrateLocal();
        // localVar created
        // localVar used
        // localVar deleted (after method ends)
        
        System.out.println("\n=== Create Object 2 ===");
        LifetimeExample obj2 = new LifetimeExample(2);
        // obj2 alive
        // instanceVar (of obj2) alive
        // staticVar = 2
        
        System.out.println("\n=== Delete Object 1 ===");
        obj1 = null;
        // obj1 deleted by Garbage Collector
        // instanceVar (of obj1) deleted
        // staticVar still = 2
        
        System.out.println("\n=== Delete Object 2 ===");
        obj2 = null;
        // obj2 deleted
        // instanceVar (of obj2) deleted
        // staticVar still = 2
        
        System.out.println("\nProgram ends, staticVar deleted");
    }
}

ตัวอย่างที่ 3: Reference Memory

javapublic class BankAccount {
    private String accountNumber;
    private double balance;
    
    public BankAccount(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }
    
    public void deposit(double amount) {
        balance += amount;
    }
    
    public void displayInfo() {
        System.out.println("Account: " + accountNumber + 
                         ", Balance: " + balance);
    }
}

// ใช้งาน
public class ReferenceDemo {
    public static void main(String[] args) {
        System.out.println("=== Primitive Type ===");
        int balance1 = 1000;
        int balance2 = balance1;
        
        balance1 = 2000;
        System.out.println("balance1: " + balance1);  // 2000
        System.out.println("balance2: " + balance2);  // 1000 (ต่างกัน)
        
        System.out.println("\n=== Reference Type ===");
        BankAccount account1 = new BankAccount("ACC001", 1000);
        BankAccount account2 = account1;  // ชี้ไปเดียวกัน
        
        account1.deposit(1000);  // เปลี่ยน account object
        
        System.out.println("Account1:");
        account1.displayInfo();  // Balance: 2000
        
        System.out.println("Account2:");
        account2.displayInfo();  // Balance: 2000 (เปลี่ยนไปด้วย!)
        
        System.out.println("\n=== Different Objects ===");
        BankAccount account3 = new BankAccount("ACC003", 1000);
        BankAccount account4 = new BankAccount("ACC004", 1000);
        
        account3.deposit(500);
        
        System.out.println("Account3:");
        account3.displayInfo();  // Balance: 1500
        
        System.out.println("Account4:");
        account4.displayInfo();  // Balance: 1000 (ต่างกัน)
    }
}

Output:

text=== Primitive Type ===
balance1: 2000
balance2: 1000

=== Reference Type ===
Account1:
Account: ACC001, Balance: 2000.0
Account2:
Account: ACC001, Balance: 2000.0

=== Different Objects ===
Account3:
Account: ACC003, Balance: 1500.0
Account4:
Account: ACC004, Balance: 1000.0

ตัวอย่างที่ 4: Array Reference

javapublic class ArrayReferenceDemo {
    public static void main(String[] args) {
        System.out.println("=== Primitive Array ===");
        int[] arr1 = {1, 2, 3};
        int[] arr2 = arr1;  // ชี้ไปเดียวกัน
        
        arr1[0] = 99;
        
        System.out.println("arr1[0]: " + arr1[0]);  // 99
        System.out.println("arr2[0]: " + arr2[0]);  // 99 (เปลี่ยน!)
        
        System.out.println("\n=== Object Array ===");
        Student[] students1 = new Student[2];
        students1[0] = new Student("John", 3.75);
        students1[1] = new Student("Jane", 3.50);
        
        Student[] students2 = students1;  // ชี้ไปเดียวกัน
        
        students1[0].updateGPA(3.85);
        
        System.out.println("students1[0] GPA: " + 
                          students1[0].getGPA());  // 3.85
        System.out.println("students2[0] GPA: " + 
                          students2[0].getGPA());  // 3.85 (เปลี่ยน!)
        
        System.out.println("\n=== New Array vs Same Reference ===");
        int[] arr3 = new int[3];  // ใหม่
        int[] arr4 = new int[3];  // ใหม่
        
        arr3[0] = 10;
        arr4[0] = 20;
        
        System.out.println("arr3[0]: " + arr3[0]);  // 10
        System.out.println("arr4[0]: " + arr4[0]);  // 20 (ต่างกัน)
    }
}

สรุป: Scope, Lifetime, Reference

Scope (ขอบเขตการเข้าถึง)

text┌─────────────────────┐
│ Class Scope         │
│ ├─ Attributes      │
│ ├─ Method Scope     │
│ │  ├─ Local Vars   │
│ │  ├─ If Block     │
│ │  ├─ For Block    │
│ │  └─ ...          │
│ └─ Method2         │
└─────────────────────┘

เอกสารประกาศ Scope ที่แคบลง = เข้าถึงได้น้อยลง

Lifetime (ช่วงชีวิต)

ประเภทจำนวนช่วงชีวิต
Static1Program start → end
Instance1 ต่อ objectObject create → delete
Localใหม่แต่ละรอบMethod/block start → end

Reference Memory

ประเภทเก็บทำสำเนา
Primitiveค่าจริงสำเนาค่า (ต่างกัน)
Referenceที่อยู่ชี้ไปเดียวกัน

ตัวอย่างสุดท้าย: รวม Scope, Lifetime, Reference

javapublic class ComprehensiveDemo {
    // CLASS SCOPE, PROGRAM LIFETIME, STATIC
    public static int globalCount = 0;
    
    // CLASS SCOPE, OBJECT LIFETIME, INSTANCE
    private String name;
    private double[] scores;
    
    public ComprehensiveDemo(String name) {
        this.name = name;
        this.scores = new double[5];
        ComprehensiveDemo.globalCount++;
    }
    
    public void addScores(double[] newScores) {
        // METHOD SCOPE, METHOD LIFETIME, REFERENCE
        // newScores reference points to array
        
        if (newScores != null) {
            // BLOCK SCOPE, BLOCK LIFETIME
            int length = newScores.length;
            
            for (int i = 0; i < length; i++) {
                // LOOP BLOCK SCOPE, LOOP BLOCK LIFETIME
                double score = newScores[i];
                this.scores[i] = score;  // REFERENCE - ชี้ไปที่ array
            }
        }
    }
    
    public void displayInfo() {
        System.out.printf("Name: %s, Count: %d\n", 
                         this.name, ComprehensiveDemo.globalCount);
        
        for (int i = 0; i < this.scores.length; i++) {
            System.out.printf("Score %d: %.1f\n", i+1, this.scores[i]);
        }
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        // globalCount allocated (static lifetime = program duration)
        
        // obj1 allocated (instance lifetime = object duration)
        ComprehensiveDemo obj1 = new ComprehensiveDemo("John");
        double[] scores1 = {85, 90, 78, 92, 88};  // reference type
        obj1.addScores(scores1);
        
        // obj2 allocated
        ComprehensiveDemo obj2 = new ComprehensiveDemo("Jane");
        double[] scores2 = {75, 80, 82, 78, 85};  // reference type
        obj2.addScores(scores2);
        
        obj1.displayInfo();
        obj2.displayInfo();
        
        // scores1, scores2 scope ends here (method scope)
        // but data still in obj1.scores, obj2.scores (instance lifetime)
        
        obj1 = null;  // obj1 deallocated (Garbage Collector)
        // but globalCount still exists (static lifetime)
        
        // globalCount deallocated when program ends
    }
}