บทนำ: ทำไมต้อง Nested Class?
เมื่อเขียนโปรแกรม บางครั้ง class เล็กๆ ต้องใช้เฉพาะ class ใหญ่ที่บรรจุมัน:
text❌ ปัญหา: Class ย่อยต่างกว่าง
├─ NodeIterator ใช้ได้เฉพาะใน LinkedList
├─ ButtonListener ใช้ได้เฉพาะใน MainFrame
├─ ตัวแปร private ต้องการให้ inner class เข้าถึง
└─ Namespace pollution (หลายชื่อเดียวกัน)
✓ วิธีแก้: Nested Class
├─ Class ขึ้นอยู่ logic
├─ ซ่อน implementation details
├─ Organize code เป็นกลุ่ม
└─ Access private members
Nested Class = Class ที่อยู่ในคลาสอื่น มี 4 แบบตามที่เห็นในการเข้าถึงและการใช้งาน
ประเภทของ Nested Class
โครงสร้างการจัดหมวดหมู่
textNested Class ทั้งหมด
│
├─ Static Nested Class
│ ├─ ไม่จำเป็นต้องมี outer instance
│ ├─ เข้าถึง static members ของ outer
│ └─ ไม่เข้าถึง instance members
│
└─ Inner Class (Non-static)
├─ ต้องมี outer instance
├─ เข้าถึง instance members
│
├─ Regular Inner Class
├─ Local Inner Class (ในเมธอด)
└─ Anonymous Inner Class (ไม่มีชื่อ)
ตัวอย่างที่ 1: Static Nested Class
คำอธิบาย
Static Nested Class เป็น class ที่ประกาศเป็น static ภายในคลาสอื่น มีลักษณะเหมือน class ธรรมดา แต่อยู่ใน namespace ของ outer class
java// ✓ Static Nested Class Example
public class LinkedList {
private Node head;
private int size;
// ✓ Static Nested Class - ไม่ต้อง LinkedList instance
public static class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
this.next = null;
}
public void displayNode() {
System.out.println("Node: " + value);
}
}
// Methods ของ LinkedList
public void addNode(int value) {
Node newNode = new Node(value); // ← ใช้ได้
if (head == null) {
head = newNode;
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
size++;
}
public void displayList() {
Node current = head;
System.out.println("LinkedList:");
while (current != null) {
current.displayNode();
current = current.next;
}
}
}
// ✓ Usage
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.addNode(10);
list.addNode(20);
list.addNode(30);
list.displayList();
// ✓ สามารถ create Node อย่างอิสระได้
LinkedList.Node standalone = new LinkedList.Node(100);
standalone.displayNode();
}
}
// Output:
// LinkedList:
// Node: 10
// Node: 20
// Node: 30
// Node: 100
// ✓ BENEFITS:
// ✓ Node ทำหน้าที่เฉพาะเจาะจง
// ✓ ชื่อ LinkedList.Node ชัดเจน
// ✓ ไม่ต้อง LinkedList instance
// ✓ Accessible นอก LinkedList ถ้าต้อง
ตัวอย่างจริง: Database Connection Factory
javapublic class Database {
// Static nested class for configuration
public static class ConnectionConfig {
private String url;
private String username;
private String password;
private int poolSize;
public ConnectionConfig(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
this.poolSize = 10; // default
}
public ConnectionConfig withPoolSize(int size) {
this.poolSize = size;
return this;
}
public String getUrl() { return url; }
public String getUsername() { return username; }
public String getPassword() { return password; }
public int getPoolSize() { return poolSize; }
}
// Static method ใช้ config
public static void initialize(ConnectionConfig config) {
System.out.println("Initializing with:");
System.out.println(" URL: " + config.getUrl());
System.out.println(" Pool size: " + config.getPoolSize());
}
}
// Usage
Database.ConnectionConfig config = new Database.ConnectionConfig(
"jdbc:mysql://localhost:3306/mydb",
"root",
"password"
);
config.withPoolSize(20);
Database.initialize(config);
ตัวอย่างที่ 2: Regular Inner Class
คำอธิบาย
Regular Inner Class คือ non-static nested class ที่ต้องการ outer class instance และสามารถเข้าถึง private members ของ outer class
java// ✓ Regular Inner Class Example
public class OuterClass {
private String outerData = "Private data from Outer";
private int count = 0;
public class InnerClass {
private String innerData = "Data from Inner";
public void displayData() {
// ✓ เข้าถึง private members ของ outer
System.out.println("Outer: " + outerData);
System.out.println("Inner: " + innerData);
}
public void incrementOuterCount() {
// ✓ เข้าถึง outer instance members
count++;
System.out.println("Outer count incremented to: " + count);
}
}
public void createInnerInstance() {
// สร้าง inner class instance
InnerClass inner = new InnerClass();
inner.displayData();
inner.incrementOuterCount();
}
}
// ✓ Usage
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// ✓ วิธีที่ 1: สร้าง inner class ผ่าน outer
outer.createInnerInstance();
// ✓ วิธีที่ 2: สร้าง inner class โดยตรง
OuterClass outer2 = new OuterClass();
OuterClass.InnerClass inner = outer2.new InnerClass();
inner.displayData();
inner.incrementOuterCount();
}
}
// Output:
// Outer: Private data from Outer
// Inner: Data from Inner
// Outer count incremented to: 1
// Outer: Private data from Outer
// Inner: Data from Inner
// Outer count incremented to: 1
// ✓ BENEFITS:
// ✓ เข้าถึง private members ของ outer
// ✓ Logical grouping ของ related classes
// ✓ ซ่อน implementation details
ตัวอย่างจริง: GUI Event Listener
javapublic class MainWindow {
private JButton submitButton;
private JTextField inputField;
private String windowTitle = "My Application";
// Inner class for button listener
public class SubmitButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// ✓ เข้าถึง outer instance
String input = inputField.getText();
System.out.println("Button clicked in: " + windowTitle);
System.out.println("Input: " + input);
processInput(input);
}
}
// Inner class for window listener
public class WindowCloseListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
System.out.println("Closing: " + windowTitle);
saveState();
System.exit(0);
}
}
public void setupUI() {
submitButton.addActionListener(new SubmitButtonListener());
addWindowListener(new WindowCloseListener());
}
private void processInput(String input) {
// Process logic
}
private void saveState() {
// Save logic
}
}
ตัวอย่างที่ 3: Local Inner Class
คำอธิบาย
Local Inner Class คือ inner class ที่ประกาศภายในเมธอด มีขอบเขตจำกัดเฉพาะในเมธอดนั้น
java// ✓ Local Inner Class Example
public class Calculator {
private int baseValue = 100;
public void calculate() {
final int localValue = 50; // ← must be effectively final
// ✓ Local Inner Class - ประกาศในเมธอด
class LocalCalculator {
public int add(int num) {
// ✓ เข้าถึง outer instance variables
// ✓ เข้าถึง local method variables
return baseValue + localValue + num;
}
public void displayResult() {
System.out.println("Base: " + baseValue);
System.out.println("Local: " + localValue);
System.out.println("Sum: " + add(20));
}
}
// ✓ ใช้ local class ในเมธอด
LocalCalculator calc = new LocalCalculator();
calc.displayResult();
}
}
// Usage
Calculator calc = new Calculator();
calc.calculate();
// Output:
// Base: 100
// Local: 50
// Sum: 170
// ✓ BENEFITS:
// ✓ Scope จำกัด - ใช้เฉพาะในเมธอด
// ✓ เข้าถึง method parameters/local variables
// ✓ เข้าถึง outer instance
// ✗ LIMITATION: ต้องใช้ variables ที่ effectively final
ตัวอย่างจริง: Temporary Processing
javapublic class DataProcessor {
public void processDataWithValidation() {
String dataSource = "database";
int retryCount = 3;
// Local inner class สำหรับ temporary use
class DataValidator {
private List<String> errors = new ArrayList<>();
public boolean validate(String data) {
if (data == null || data.isEmpty()) {
errors.add("Data cannot be empty");
return false;
}
if (data.length() > 100) {
errors.add("Data too long");
return false;
}
return true;
}
public void printErrors() {
errors.forEach(System.out::println);
}
}
DataValidator validator = new DataValidator();
String testData = "Hello World";
if (validator.validate(testData)) {
System.out.println("Validation passed!");
} else {
validator.printErrors();
}
}
}
ตัวอย่างที่ 4: Anonymous Inner Class
คำอธิบาย
Anonymous Inner Class คือ inner class ที่ไม่มีชื่อ ประกาศและสร้าง instance ในคำสั่งเดียว เหมาะสำหรับการใช้งานครั้งเดียว
java// ✓ Anonymous Inner Class Example
public interface Greeting {
void greet(String name);
}
public class GreetingApp {
public void runGreeting() {
// ✓ Anonymous Inner Class
Greeting english = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hello, " + name);
}
};
english.greet("John");
// ✓ อีก anonymous class สำหรับ greeting อื่น
Greeting thai = new Greeting() {
@Override
public void greet(String name) {
System.out.println("สวัสดี, " + name);
}
};
thai.greet("สมชาย");
}
}
// Usage
GreetingApp app = new GreetingApp();
app.runGreeting();
// Output:
// Hello, John
// สวัสดี, สมชาย
// ✓ BENEFITS:
// ✓ Concise - define and use ในคำสั่งเดียว
// ✓ No separate class file
// ✗ LIMITATION: ไม่มีชื่อ ใช้ครั้งเดียว
ตัวอย่างจริง: Event Handling
javapublic class UserInterface {
private JButton saveButton;
private JButton cancelButton;
private UserData userData;
public void setupButtons() {
// ✓ Anonymous inner class for save button
saveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Saving user data...");
userData.save();
System.out.println("Data saved!");
}
});
// ✓ Anonymous inner class for cancel button
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Canceling operation...");
userData.reset();
}
});
}
}
// ✓ ตัวอย่าง: Thread หรือ Runnable
public void startBackgroundTask() {
// Anonymous inner class สำหรับ thread
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Background task running...");
try {
Thread.sleep(2000);
System.out.println("Background task completed!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
backgroundThread.start();
}
ตัวอย่างที่ 5: เปรียบเทียบทั้ง 4 ประเภท
Diagram เปรียบเทียบ
text┌─────────────────────────────────────────┐
│ Nested Class Types │
├─────────────────────────────────────────┤
│ │
│ 1. STATIC NESTED CLASS │
│ ├─ static class Inner { } │
│ ├─ ไม่ต้อง outer instance │
│ ├─ เข้า static members เท่านั้น │
│ └─ ใช้: Helper classes, factories │
│ │
│ 2. REGULAR INNER CLASS │
│ ├─ class Inner { } │
│ ├─ ต้อง outer instance │
│ ├─ เข้า instance + static members │
│ └─ ใช้: Event listeners, adapters │
│ │
│ 3. LOCAL INNER CLASS │
│ ├─ class Inner { } ในเมธอด │
│ ├─ ขอบเขต = เมธอดเท่านั้น │
│ ├─ เข้า method variables │
│ └─ ใช้: Temporary calculations │
│ │
│ 4. ANONYMOUS INNER CLASS │
│ ├─ new Interface() { } │
│ ├─ ไม่มีชื่อ ใช้ครั้งเดียว │
│ ├─ ประกาศและใช้พร้อมกัน │
│ └─ ใช้: Event handlers, callbacks │
│ │
└─────────────────────────────────────────┘
ตารางเปรียบเทียบ
text┌──────────────┬──────────┬─────────┬──────────┬─────────────┐
│ ประเภท │ Static? │ Instance│ Scope │ ใช้เมื่อไหร่ │
├──────────────┼──────────┼─────────┼──────────┼─────────────┤
│ Static │ Yes │ ไม่ต้อง │ Class │ Helper │
│ Regular │ No │ ต้อง │ Class │ Listener │
│ Local │ No │ ต้อง │ Method │ Temp │
│ Anonymous │ No │ ต้อง │ One-time │ Callback │
└──────────────┴──────────┴─────────┴──────────┴─────────────┘
ตัวอย่างการเลือกประเภท
java// ✓ ตัวอย่างที่ 1: Static - ต้องสร้างคำอธิบายสำหรับหน้าเว็บ
public class WebPage {
// ✓ Static nested class
public static class MetaData {
private String title;
private String description;
public MetaData(String title, String description) {
this.title = title;
this.description = description;
}
}
public void createPage() {
// ไม่ต้อง WebPage instance
WebPage.MetaData meta = new WebPage.MetaData(
"Home Page",
"Welcome to our website"
);
}
}
// ✓ ตัวอย่างที่ 2: Regular - Event listener ต้อง access outer
public class FormWindow {
private JTextField nameField;
public class FormSubmitListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// ต้อง access nameField จาก outer
String name = nameField.getText();
}
}
}
// ✓ ตัวอย่างที่ 3: Local - Calculation เฉพาะในเมธอด
public class SalesReport {
public void generateReport() {
final int discountRate = 10;
class Calculator {
public double calculate(double price) {
return price * (100 - discountRate) / 100;
}
}
Calculator calc = new Calculator();
System.out.println("Discounted: " + calc.calculate(1000));
}
}
// ✓ ตัวอย่างที่ 4: Anonymous - One-time callback
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("Button clicked!");
}
});
Best Practices: Nested Classes
text✓ ควรใช้ Static Nested Class เมื่อ:
☑ Inner class ไม่ต้อง access outer members
☑ Inner class ใช้ได้อิสระ
☑ ต้องการ organize code เป็น namespace
☑ เช่น: Helper classes, config classes
✓ ควรใช้ Regular Inner Class เมื่อ:
☑ Inner class ต้อง access outer members
☑ ความสัมพันธ์ "is part of" outer
☑ เช่น: Event listeners, validators
✓ ควรใช้ Local Inner Class เมื่อ:
☑ Inner class ใช้เฉพาะในเมธอด
☑ Scope ชัดเจน
☑ เช่น: Temporary calculations
✓ ควรใช้ Anonymous Inner Class เมื่อ:
☑ ใช้งานครั้งเดียว
☑ Implementation ง่ายไม่ซับซ้อน
☑ เช่น: Event handlers, callbacks
✗ หลีกเลี่ยง:
☐ ใช้ nested class ต่อเนื่องหลายครั้ง
☐ Anonymous class ที่ complex (>10 lines)
☐ Circular dependencies ระหว่าง inner-outer
☐ สร้าง nested class เมื่อไม่จำเป็น
สรุป
Nested Class & Inner Class เป็น advanced feature ที่ช่วยให้ code organized และ clean:
4 ประเภท Nested Class:
- Static Nested Class – ไม่ต้อง outer instance
- Regular Inner Class – เข้าถึง instance members
- Local Inner Class – ขอบเขตจำกัดในเมธอด
- Anonymous Inner Class – ไม่มีชื่อ ใช้ครั้งเดียว
ประโยชน์:
- Logical Grouping – organize related classes
- Encapsulation – ซ่อน implementation details
- Access Control – เข้าถึง private members
- Namespace Organization – ชัดเจนความสัมพันธ์
กฎการเลือก:
- ใช้ Static ถ้าไม่ต้อง outer instance
- ใช้ Regular ถ้าต้อง access outer
- ใช้ Local ถ้าใช้เฉพาะในเมธอด
- ใช้ Anonymous ถ้าใช้ครั้งเดียว
Nested classes มี role สำคัญในการเขียน clean, organized code – ระบบประเภทนี้อยู่ในทุก enterprise application ถ้า master nested classes ได้ code ของคุณจะดูมาก professional และ maintainable
