0Pricing
Java Academy · บทเรียน

คลาสภายในและการเข้าถึงคลาสภายนอก

ใช้คลาสภายในที่ไม่ใช่ static ซึ่งเก็บการอ้างอิงไปยังอินสแตนซ์ของคลาสภายนอก

คลาสภายในและการเข้าถึงคลาสภายนอก เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คลาสภายใน

คลาสภายในที่ไม่ใช่สแตติกจะเก็บการอ้างอิงโดยปริยายไปยังอินสแตนซ์ของคลาสภายนอกที่ห่อหุ้มอยู่ คลาสนี้สามารถเข้าถึงสมาชิกทั้งหมดของคลาสภายนอก รวมถึงสมาชิก private

การประกาศและใช้งานคลาสภายใน

สร้างอินสแตนซ์ของคลาสภายในได้เฉพาะผ่านอินสแตนซ์ของคลาสภายนอก โดยใช้ outer.new InnerClass()

class BankAccount {
    private double balance;
    private String owner;

    BankAccount(String owner, double initial) {
        this.owner = owner; this.balance = initial;
    }

    class Transaction {
        void deposit(double amount) {
            balance += amount; // accesses outer field directly
            System.out.println(owner + " deposited " + amount);
        }
    }
}

BankAccount acct = new BankAccount("Alice", 500);
BankAccount.Transaction tx = acct.new Transaction();
tx.deposit(100); // Alice deposited 100.0

การเข้าถึงสมาชิกของคลาสภายนอก

คลาสภายในสามารถเข้าถึงสมาชิกทั้งหมดของคลาสภายนอกได้ — แม้แต่ฟิลด์และเมธอด private

class Encryption {
    private final String secretKey = "s3cr3t"; // private!

    class Encryptor {
        String encrypt(String data) {
            // Inner class accesses private outer field
            return data + "|" + secretKey.hashCode();
        }
    }

    class Decryptor {
        String decrypt(String encrypted) {
            int idx = encrypted.lastIndexOf('|');
            return encrypted.substring(0, idx);
        }
    }
}

การบดบังชื่อด้วย this

เมื่อคลาสภายในมีฟิลด์ชื่อเดียวกับคลาสภายนอก ให้ใช้ OuterClass.this.field เพื่ออ้างอิงฟิลด์ของคลาสภายนอก

class Outer {
    int value = 10;

    class Inner {
        int value = 20; // shadows outer.value

        void printBoth() {
            System.out.println("Inner value: " + value);         // 20
            System.out.println("Outer value: " + Outer.this.value); // 10
        }
    }
}

new Outer().new Inner().printBoth();
// Inner value: 20
// Outer value: 10

รูปแบบตัววนซ้ำด้วยคลาสภายใน

กรณีใช้งานแบบดั้งเดิมคือ การสร้างตัววนซ้ำเป็นคลาสภายในที่เข้าถึงข้อมูล private ของคอลเลกชันที่ห่อหุ้มอยู่

class NumberRange implements Iterable<Integer> {
    private final int start;
    private final int end;

    NumberRange(int start, int end) {
        this.start = start; this.end = end;
    }

    @Override
    public java.util.Iterator<Integer> iterator() {
        return new RangeIterator(); // inner class instance
    }

    private class RangeIterator implements java.util.Iterator<Integer> {
        private int current = start; // accesses outer start

        public boolean hasNext() { return current <= end; }
        public Integer next()    { return current++; }
    }
}

for (int n : new NumberRange(1, 5)) System.out.print(n + " ");
// 1 2 3 4 5

ตัวรับฟังเหตุการณ์เป็นคลาสภายใน

การเขียนโปรแกรม GUI และการเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์มักใช้คลาสภายในเพื่อสร้างตัวรับฟังเหตุการณ์ ซึ่งสามารถเข้าถึงสถานะขององค์ประกอบส่วนติดต่อผู้ใช้ที่ห่อหุ้มอยู่

// Swing-style example (conceptual)
class LoginPanel {
    private String username = "";

    class LoginButtonListener {
        void onButtonClicked() {
            if (username.isEmpty()) {
                System.out.println("Please enter username"); // accesses outer field
            } else {
                System.out.println("Logging in as: " + username);
            }
        }
    }

    void setUsername(String name) { this.username = name; }
}
LoginPanel panel = new LoginPanel();
panel.setUsername("alice");
panel.new LoginButtonListener().onButtonClicked();
// Logging in as: alice

คลาสภายในในคอลเลกชัน

การใช้งานคอลเลกชันบางรูปแบบใช้คลาสภายในเพื่อเก็บตัวชี้ตำแหน่งหรือมุมมองที่มีข้อมูลจากคอลเลกชันที่ห่อหุ้มอยู่

class SimpleStack<T> {
    private Object[] elements;
    private int size = 0;

    SimpleStack(int capacity) {
        elements = new Object[capacity];
    }

    void push(T item) {
        if (size >= elements.length) throw new StackOverflowError();
        elements[size++] = item;
    }

    T pop() {
        if (size == 0) throw new java.util.EmptyStackException();
        T item = (T) elements[--size];
        elements[size] = null;
        return item;
    }

    int size() { return size; }
}

คำเตือนเรื่องหน่วยความจำรั่ว

อินสแตนซ์ของคลาสภายในจะเก็บการอ้างอิงไปยังอินสแตนซ์ภายนอก หากคลาสภายในหลุดออกไปจากคลาสภายนอก (เช่น ถูกเก็บไว้ในคอลเลกชันที่มีอายุการใช้งานยาวนาน) อินสแตนซ์ภายนอกจะไม่สามารถถูกเก็บคืนโดยตัวเก็บขยะได้

// Memory leak: anonymous inner class (implicit outer ref) stored statically
class LeakExample {
    byte[] data = new byte[1024 * 1024]; // 1MB

    // BAD: this Runnable holds a reference to LeakExample.this
    Runnable leak = new Runnable() {
        public void run() { System.out.println(data.length); }
    };
}
// If 'leak' is stored somewhere, LeakExample (and its 1MB) is never GC'd

// FIX: use a static nested class or a lambda that captures only needed values
static Runnable noLeak(byte[] data) {
    return () -> System.out.println(data.length); // captures array, not outer
}

การเปรียบเทียบคลาสภายในกับแลมบ์ดา

แลมบ์ดาของ Java สมัยใหม่มักใช้แทนคลาสภายในนิรนามสำหรับอินเทอร์เฟซที่มีเมธอดเดียว แลมบ์ดากระชับกว่า แต่ไม่มี this เป็นของตนเอง

// Old way: anonymous inner class
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running (inner class) — this is the Runnable");
    }
};

// Modern: lambda
Runnable r2 = () -> System.out.println("Running (lambda)");

r1.run();
r2.run();

// Lambda this = enclosing class (not the Runnable)
// Inner class this = the Runnable instance itself

เมื่อใดควรใช้คลาสภายใน

ใช้คลาสภายในที่ไม่ใช่สแตติกเมื่อ:

  • คลาสนั้นจำเป็นต้องเข้าถึงสถานะ private ของอินสแตนซ์ที่ห่อหุ้มอยู่
  • อินสแตนซ์ภายในแต่ละตัวมีความสัมพันธ์เชิงตรรกะกับอินสแตนซ์ภายนอกเฉพาะตัว
  • ใช้รูปแบบตัววนซ้ำเพื่อห่อหุ้มตรรกะการวนสำรวจ

เชิงปฏิบัติ: Paginator

ตัวแบ่งหน้าเชิงปฏิบัติที่คลาสภายในเก็บสถานะของหน้าและเข้าถึงรายการข้อมูลของคลาสภายนอก

import java.util.*;

class Paginator<T> {
    private final List<T> items;
    private final int pageSize;

    Paginator(List<T> items, int pageSize) {
        this.items = items; this.pageSize = pageSize;
    }

    class Page {
        private int pageIndex = 0;

        List<T> current() {
            int start = pageIndex * pageSize;
            int end = Math.min(start + pageSize, items.size()); // outer
            return start < items.size() ? items.subList(start, end) : List.of();
        }
        boolean hasNext() { return (pageIndex + 1) * pageSize < items.size(); }
        void next() { if (hasNext()) pageIndex++; }
    }
}
Paginator<String> p = new Paginator<>(List.of("a","b","c","d","e"), 2);
Paginator.Page page = p.new Page();
System.out.println(page.current()); // [a, b]
page.next();
System.out.println(page.current()); // [c, d]

ตรวจสอบความเข้าใจอย่างรวดเร็ว

คลาสภายใน (ไม่ใช่สแตติก) เก็บสิ่งใดที่คลาสซ้อนแบบสแตติกไม่มี

ทบทวน: คลาสภายในและการเข้าถึงคลาสภายนอก

ประเด็นสำคัญ:

  • คลาสภายในแบบไม่ใช่สแตติกเก็บการอ้างอิงโดยนัยไปยังอินสแตนซ์ภายนอกที่ครอบอยู่
  • สร้างอินสแตนซ์ของคลาสภายในผ่านอินสแตนซ์ภายนอกเท่านั้น: outer.new Inner()
  • คลาสภายในเข้าถึงสมาชิกทั้งหมดของคลาสภายนอกได้ รวมถึงฟิลด์ส่วนตัว
  • ใช้ Outer.this.field เพื่อแยกความแตกต่างของฟิลด์ที่มีชื่อซ้ำบังกัน
  • กรณีใช้งานที่เหมาะสมที่สุด: Iterator, ตัวรับฟังเหตุการณ์ และโครงสร้างข้อมูลภายใน
  • ความเสี่ยง: อินสแตนซ์ของคลาสภายในจะป้องกันไม่ให้อินสแตนซ์ภายนอกถูกเก็บคืนโดยตัวเก็บขยะ

คำถามที่พบบ่อย

บทเรียน “คลาสภายในและการเข้าถึงคลาสภายนอก” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “คลาสภายในและการเข้าถึงคลาสภายนอก” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “คลาสภายในและการเข้าถึงคลาสภายนอก”

ใช้คลาสภายในที่ไม่ใช่ static ซึ่งเก็บการอ้างอิงไปยังอินสแตนซ์ของคลาสภายนอก คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “คลาสภายในและการเข้าถึงคลาสภายนอก” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. คลาสซ้อนแบบ Static
  2. คลาสภายในและการเข้าถึงคลาสภายนอก
  3. คลาสเฉพาะที่และคลาสนิรนาม
  4. การเลือกกลยุทธ์การซ้อนคลาสที่เหมาะสม
← กลับไปที่ Java Academy