Nested Class & Inner Class

บทนำ: ทำไมต้อง 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:

  1. Static Nested Class – ไม่ต้อง outer instance
  2. Regular Inner Class – เข้าถึง instance members
  3. Local Inner Class – ขอบเขตจำกัดในเมธอด
  4. 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