คลาสภายในและการเข้าถึงคลาสภายนอก
ใช้คลาสภายในที่ไม่ใช่ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- คลาสซ้อนแบบ Static
- คลาสภายในและการเข้าถึงคลาสภายนอก
- คลาสเฉพาะที่และคลาสนิรนาม
- การเลือกกลยุทธ์การซ้อนคลาสที่เหมาะสม