Inner Classes (Lớp lồng nhau)
Bài trước: Interface — Đã học cách định nghĩa contract với interface. Bài này sẽ học về inner classes - các class được định nghĩa bên trong class khác để tổ chức code tốt hơn.
Sau bài này, bạn sẽ:
- Phân biệt 4 loại inner class: Member, Static Nested, Local, và Anonymous
- Sử dụng member inner class để access instance members của outer class
- Tạo static nested class cho helper classes không cần outer instance
- Áp dụng local inner class và anonymous inner class cho one-time use
- So sánh lambda expression vs anonymous inner class và biết khi nào dùng loại nào
Tại sao cần Inner Classes?
Inner class (nested class) là class được định nghĩa bên trong một class khác.
Lý do sử dụng
// Ví dụ: Node chỉ có ý nghĩa trong LinkedList
class LinkedList {
// Inner class - Node chỉ dùng trong LinkedList
private class Node {
int data;
Node next;
Node(int data) {
this.data = data;
this.next = null;
}
}
private Node head;
public void add(int data) {
Node newNode = new Node(data); // Tạo Node ở đây
if (head == null) {
head = newNode;
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
}
}
- Logical grouping: Nhóm các classes liên quan với nhau
- Encapsulation: Ẩn implementation details
- Code readability: Code gọn gàng, dễ đọc hơn
- Access to outer class: Truy cập được private members của outer class
- Implement callbacks: Sử dụng trong event handling
Các loại Inner Classes
Java có 4 loại inner classes:
| Loại | Đặc điểm | Khi nào dùng |
|---|---|---|
| Member Inner Class | Non-static, member của outer class | Cần access instance members của outer class |
| Static Nested Class | Static member của outer class | Không cần access instance members |
| Local Inner Class | Định nghĩa trong method/block | Chỉ dùng trong method đó |
| Anonymous Inner Class | Không có tên, tạo tại chỗ | One-time use, implement interface/extend class |
1. Member Inner Class (Non-static Nested Class)
Class non-static được định nghĩa như một member của outer class.
Đặc điểm
class OuterClass {
private int outerData = 10;
private static int staticData = 20;
// Member inner class
class InnerClass {
private int innerData = 30;
public void display() {
// Truy cập được tất cả members của outer class
System.out.println("Outer data: " + outerData);
System.out.println("Static data: " + staticData);
System.out.println("Inner data: " + innerData);
}
public void accessOuterMethod() {
outerMethod(); // Gọi method của outer class
}
}
public void outerMethod() {
System.out.println("Outer method called");
}
public void createInner() {
InnerClass inner = new InnerClass();
inner.display();
}
}
// Sử dụng
public class TestMemberInner {
public static void main(String[] args) {
// Tạo outer object trước
OuterClass outer = new OuterClass();
// Tạo inner object thông qua outer
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
// Hoặc gọi method của outer
outer.createInner();
}
}
Ví dụ thực tế: Car và Engine
class Car {
private String brand;
private int year;
public Car(String brand, int year) {
this.brand = brand;
this.year = year;
}
// Inner class Engine
class Engine {
private int horsepower;
private String type;
public Engine(int horsepower, String type) {
this.horsepower = horsepower;
this.type = type;
}
public void start() {
// Truy cập được private members của Car
System.out.println(brand + " " + year + " engine starting...");
System.out.println("Type: " + type);
System.out.println("Power: " + horsepower + " HP");
}
public void displayCarInfo() {
System.out.println("This engine belongs to " + brand);
}
}
public void createEngine(int hp, String type) {
Engine engine = new Engine(hp, type);
engine.start();
}
}
public class CarDemo {
public static void main(String[] args) {
Car car = new Car("Toyota", 2023);
// Tạo engine thông qua car object
Car.Engine engine = car.new Engine(200, "V6");
engine.start();
engine.displayCarInfo();
}
}
- Member inner class không thể có static members (trừ static final constants)
- Cần outer class instance để tạo inner class instance
- Inner class có thể access tất cả members của outer (kể cả private)
2. Static Nested Class
Class static được định nghĩa bên trong outer class.
Đặc điểm
class OuterClass {
private int outerData = 10;
private static int staticData = 20;
// Static nested class
static class StaticNestedClass {
private int nestedData = 30;
public void display() {
// Chỉ truy cập static members của outer class
// System.out.println(outerData); // ❌ COMPILE ERROR
System.out.println("Static data: " + staticData); // ✅ OK
System.out.println("Nested data: " + nestedData);
}
public static void staticMethod() {
System.out.println("Static nested class static method");
}
}
}
// Sử dụng
public class TestStaticNested {
public static void main(String[] args) {
// Không cần outer instance
OuterClass.StaticNestedClass nested =
new OuterClass.StaticNestedClass();
nested.display();
// Gọi static method
OuterClass.StaticNestedClass.staticMethod();
}
}
Ví dụ thực tế: Builder Pattern
class Person {
// Required parameters
private final String firstName;
private final String lastName;
// Optional parameters
private final int age;
private final String phone;
private final String address;
// Private constructor - chỉ Builder có thể gọi
private Person(Builder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
@Override
public String toString() {
return "Person{" +
"name='" + firstName + " " + lastName + "', " +
"age=" + age + ", " +
"phone='" + phone + "', " +
"address='" + address + "'}";
}
// Static nested Builder class
public static class Builder {
// Required parameters
private final String firstName;
private final String lastName;
// Optional parameters - initialized to default values
private int age = 0;
private String phone = "";
private String address = "";
public Builder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Person build() {
return new Person(this);
}
}
}
// Sử dụng Builder
public class BuilderDemo {
public static void main(String[] args) {
// Fluent API
Person person1 = new Person.Builder("John", "Doe")
.age(30)
.phone("123-456-7890")
.address("123 Main St")
.build();
Person person2 = new Person.Builder("Jane", "Smith")
.age(25)
.build(); // Không cần set tất cả optional fields
System.out.println(person1);
System.out.println(person2);
}
}
Member Inner vs Static Nested
| Đặc điểm | Member Inner Class | Static Nested Class |
|---|---|---|
| static keyword | ❌ Không | ✅ Có |
| Access outer instance members | ✅ Có | ❌ Không |
| Access outer static members | ✅ Có | ✅ Có |
| Tạo instance | Cần outer instance | Không cần outer instance |
| static members | ❌ Không (trừ constants) | ✅ Có |
3. Local Inner Class
Class được định nghĩa bên trong một method hoặc scope block.
Đặc điểm
class OuterClass {
private int outerData = 10;
public void outerMethod() {
final int localVar = 20; // Effectively final
// Local inner class - chỉ visible trong method này
class LocalInnerClass {
public void display() {
System.out.println("Outer data: " + outerData);
System.out.println("Local var: " + localVar);
}
}
// Sử dụng local inner class
LocalInnerClass local = new LocalInnerClass();
local.display();
}
public void anotherMethod() {
// LocalInnerClass không visible ở đây
// LocalInnerClass local = new LocalInnerClass(); // ❌ ERROR
}
}
public class TestLocalInner {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
Ví dụ thực tế: Validator
class FormValidator {
public boolean validate(String input, String type) {
// Local inner class cho validation logic
class EmailValidator {
public boolean isValid(String email) {
return email.contains("@") && email.contains(".");
}
}
class PhoneValidator {
public boolean isValid(String phone) {
return phone.matches("\\d{3}-\\d{3}-\\d{4}");
}
}
// Sử dụng local classes
switch (type.toLowerCase()) {
case "email":
EmailValidator emailValidator = new EmailValidator();
return emailValidator.isValid(input);
case "phone":
PhoneValidator phoneValidator = new PhoneValidator();
return phoneValidator.isValid(input);
default:
return false;
}
}
}
public class ValidatorDemo {
public static void main(String[] args) {
FormValidator validator = new FormValidator();
System.out.println(validator.validate("[email protected]", "email")); // true
System.out.println(validator.validate("invalid-email", "email")); // false
System.out.println(validator.validate("123-456-7890", "phone")); // true
System.out.println(validator.validate("123456", "phone")); // false
}
}
Effectively Final — Giải thích chi tiết
Effectively final là biến không được khai báo final nhưng không bao giờ thay đổi giá trị sau khi khởi tạo.
public class EffectivelyFinalDemo {
public void demonstrateEffectivelyFinal() {
int effectivelyFinalVar = 10; // Không có final keyword
// effectivelyFinalVar = 20; // ❌ Nếu uncomment → không còn effectively final
final int explicitFinalVar = 20; // Có final keyword
int notFinalVar = 30;
class LocalClass {
public void print() {
// ✅ OK - effectively final
System.out.println(effectivelyFinalVar);
// ✅ OK - explicit final
System.out.println(explicitFinalVar);
// ❌ Compile error nếu notFinalVar bị reassign
System.out.println(notFinalVar);
}
}
notFinalVar = 40; // ❌ Nếu uncomment → LocalClass.print() compile error
LocalClass local = new LocalClass();
local.print();
}
}
Local variable được capture bởi local/anonymous class phải final hoặc effectively final để đảm bảo thread-safety và consistency.
Giống hợp đồng ngầm: Biến không cần ký "final" trên giấy, nhưng hành vi của nó như đã ký — không thay đổi sau khi khởi tạo.
Tại sao cần Effectively Final?
public Runnable createRunnable() {
int counter = 0;
// ❌ Nếu cho phép capture non-final variable
return new Runnable() {
@Override
public void run() {
System.out.println(counter); // counter value nào?
}
};
// counter có thể thay đổi sau khi Runnable được tạo
// → Giá trị counter trong run() không xác định
}
// ✅ Với effectively final
public Runnable createRunnable() {
int counter = 0; // Effectively final
return new Runnable() {
@Override
public void run() {
System.out.println(counter); // Luôn = 0
}
};
}
- Chỉ visible trong method/block định nghĩa nó
- Có thể access local variables nếu chúng là final hoặc effectively final
- Không thể có access modifiers (public, private, protected)
- Không thể là static
4. Anonymous Inner Class
Class không có tên, được tạo và sử dụng tại chỗ.
Implement Interface
interface Greeting {
void greet(String name);
}
public class AnonymousDemo {
public static void main(String[] args) {
// Anonymous inner class implement interface
Greeting greeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hello, " + name + "!");
}
};
greeting.greet("John"); // Hello, John!
// Sử dụng trực tiếp
new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hi, " + name + "!");
}
}.greet("Jane"); // Hi, Jane!
}
}
Extend Class
abstract class Animal {
abstract void makeSound();
}
public class AnonymousExtendDemo {
public static void main(String[] args) {
// Anonymous inner class extend abstract class
Animal dog = new Animal() {
@Override
void makeSound() {
System.out.println("Woof! Woof!");
}
};
Animal cat = new Animal() {
@Override
void makeSound() {
System.out.println("Meow! Meow!");
}
};
dog.makeSound(); // Woof! Woof!
cat.makeSound(); // Meow! Meow!
}
}
Ví dụ thực tế: Event Handling
interface ClickListener {
void onClick();
}
class Button {
private String label;
private ClickListener clickListener;
public Button(String label) {
this.label = label;
}
public void setOnClickListener(ClickListener listener) {
this.clickListener = listener;
}
public void click() {
System.out.println("Button '" + label + "' clicked");
if (clickListener != null) {
clickListener.onClick();
}
}
}
public class EventHandlingDemo {
public static void main(String[] args) {
Button submitButton = new Button("Submit");
Button cancelButton = new Button("Cancel");
// Anonymous inner class cho event handling
submitButton.setOnClickListener(new ClickListener() {
@Override
public void onClick() {
System.out.println("Form submitted!");
}
});
cancelButton.setOnClickListener(new ClickListener() {
@Override
public void onClick() {
System.out.println("Form cancelled!");
}
});
submitButton.click();
cancelButton.click();
}
}
Ví dụ: Thread với Anonymous Class
public class ThreadDemo {
public static void main(String[] args) {
// Anonymous inner class implement Runnable
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 1: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 2: " + i);
try {
Thread.sleep(700);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread1.start();
thread2.start();
}
}
So sánh các loại Inner Classes
| Loại | Định nghĩa | Access outer members | Access modifiers | Khi nào dùng |
|---|---|---|---|---|
| Member Inner | Trong outer class | Instance + static | ✅ Có | Cần access outer instance |
| Static Nested | Trong outer class (static) | Chỉ static | ✅ Có | Không cần outer instance |
| Local Inner | Trong method/block | Có (final/effectively final locals) | ❌ Không | Chỉ dùng trong method |
| Anonymous | Tại chỗ | Có | ❌ Không | One-time use, callbacks |
Scope Rules — Inner Class Accessing Outer Class Members
Member inner class có thể access tất cả members của outer class (kể cả private):
Qualified this
class Outer {
private String outerField = "Outer field";
class Inner {
private String innerField = "Inner field";
// Nếu Inner cũng có field cùng tên
private String outerField = "Inner's outerField";
public void demonstrate() {
// this → Inner's instance
System.out.println(this.outerField); // Output: Inner's outerField
// Outer.this → Outer's instance
System.out.println(Outer.this.outerField); // Output: Outer field
// Có thể gọi Outer's methods
Outer.this.outerMethod();
}
}
private void outerMethod() {
System.out.println("Outer method called from Inner");
}
public void createInner() {
Inner inner = new Inner();
inner.demonstrate();
}
}
public class ScopeDemo {
public static void main(String[] args) {
Outer outer = new Outer();
outer.createInner();
}
}
Static Nested Class vs Inner Class — Access Differences
class OuterAccess {
private static int staticField = 10;
private int instanceField = 20;
private static void staticMethod() {
System.out.println("Static method");
}
private void instanceMethod() {
System.out.println("Instance method");
}
// Static nested class
static class StaticNested {
public void access() {
System.out.println(staticField); // ✅ OK
staticMethod(); // ✅ OK
// System.out.println(instanceField); // ❌ Compile error!
// instanceMethod(); // ❌ Compile error!
}
}
// Member inner class
class MemberInner {
public void access() {
System.out.println(staticField); // ✅ OK
staticMethod(); // ✅ OK
System.out.println(instanceField); // ✅ OK
instanceMethod(); // ✅ OK
}
}
}
Static nested class giống "người thuê nhà" — biết địa chỉ nhà (static members) nhưng không vào được phòng riêng (instance members).
Member inner class giống "thành viên gia đình" — có chìa khóa tất cả phòng (access tất cả).
Serialization Issues với Inner Classes
Inner classes (đặc biệt là non-static) có vấn đề khi serialize:
import java.io.*;
class Outer implements Serializable {
private static final long serialVersionUID = 1L;
private String outerData = "Outer";
// ⚠️ Non-static inner class serialization có vấn đề
class Inner implements Serializable {
private static final long serialVersionUID = 1L;
private String innerData = "Inner";
// Inner class tự động giữ reference đến Outer instance
// → Serialize Inner cũng serialize Outer
}
// ✅ Static nested class OK cho serialization
static class StaticNested implements Serializable {
private static final long serialVersionUID = 1L;
private String data = "Static Nested";
}
}
public class SerializationDemo {
public static void main(String[] args) {
// ⚠️ Serialize inner class
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("inner.ser"))) {
out.writeObject(inner);
// Cảnh báo: Serialize inner cũng serialize outer instance
} catch (IOException e) {
e.printStackTrace();
}
// ✅ Serialize static nested class (không vấn đề)
Outer.StaticNested nested = new Outer.StaticNested();
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("nested.ser"))) {
out.writeObject(nested);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- Tránh serialize non-static inner classes nếu không cần thiết
- Nếu bắt buộc, đảm bảo outer class cũng
Serializable - Ưu tiên static nested classes cho serializable objects
- Xem xét dùng separate top-level classes cho serialization
Lambda Expressions vs Anonymous Inner Classes
Lambda expressions (Java 8+) là cách ngắn gọn hơn để viết anonymous inner class cho functional interface.
So sánh Chi tiết
| Đặc điểm | Lambda Expression | Anonymous Inner Class |
|---|---|---|
| Syntax | Ngắn gọn: (a, b) -> a + b | Dài dòng: new Interface() { ... } |
| this reference | Trỏ đến enclosing class | Trỏ đến anonymous class |
| Compile output | invokedynamic bytecode | Separate .class file |
| Performance | Nhanh hơn (lazy initialization) | Chậm hơn (eager class loading) |
| Functional interface | ✅ Chỉ dùng với functional interface | ✅ Dùng với bất kỳ interface/class |
| State | ❌ Không có fields | ✅ Có thể có fields |
| Multiple methods | ❌ Chỉ 1 method | ✅ Nhiều methods |
this Reference Difference
public class ThisReferenceDemo {
private String field = "Enclosing class";
public void demonstrateLambda() {
Runnable lambda = () -> {
// this → ThisReferenceDemo instance
System.out.println(this.field); // Output: Enclosing class
};
lambda.run();
}
public void demonstrateAnonymous() {
Runnable anonymous = new Runnable() {
private String field = "Anonymous class";
@Override
public void run() {
// this → Runnable anonymous class instance
System.out.println(this.field); // Output: Anonymous class
// Access enclosing class
System.out.println(ThisReferenceDemo.this.field); // Output: Enclosing class
}
};
anonymous.run();
}
}
Khi nào dùng Lambda vs Anonymous Class?
Dùng Lambda khi:
- Interface là functional interface (1 abstract method)
- Code đơn giản, ngắn gọn
- Không cần access
thiscủa anonymous class - Performance quan trọng
Dùng Anonymous Class khi:
- Interface có nhiều abstract methods
- Cần implement multiple methods
- Cần fields hoặc state
- Cần access
thiscủa anonymous class - Extend abstract class
interface Worker {
void work();
void rest(); // Nhiều methods - không thể dùng lambda
}
public class LambdaLimitation {
public static void main(String[] args) {
// Phải dùng anonymous class
Worker worker = new Worker() {
@Override
public void work() {
System.out.println("Working...");
}
@Override
public void rest() {
System.out.println("Resting...");
}
};
worker.work();
worker.rest();
}
}
Bẫy 1: Access difference
class Outer {
private int instanceVar = 10;
private static int staticVar = 20;
static class StaticNested {
void access() {
// ❌ Compile error! Không access instance members
// System.out.println(instanceVar);
// ✅ OK - access static members
System.out.println(staticVar);
}
}
class Inner {
void access() {
// ✅ Access cả instance và static members
System.out.println(instanceVar);
System.out.println(staticVar);
}
}
}
Bẫy 2: this reference trong inner class
class Outer {
private String name = "Outer";
class Inner {
private String name = "Inner";
void print() {
System.out.println(name); // Output: Inner
System.out.println(this.name); // Output: Inner
System.out.println(Outer.this.name); // Output: Outer
}
}
}
Bẫy 3: Lambda vs Anonymous Class với effectively final
public void test() {
int count = 0;
// ❌ Compile error cho cả lambda và anonymous class
// count++; // Làm count không còn effectively final
Runnable lambda = () -> System.out.println(count); // ✅ OK
Runnable anonymous = new Runnable() {
@Override
public void run() {
System.out.println(count); // ✅ OK
}
};
// ❌ Nếu uncomment → cả lambda và anonymous class đều compile error
// count++;
}
Ví dụ thực tế: Callback Pattern
interface Callback {
void onSuccess(String result);
void onError(String error);
}
class AsyncTask {
public void execute(String task, Callback callback) {
System.out.println("Executing task: " + task);
// Simulate async operation
try {
Thread.sleep(1000);
// Simulate success or failure
if (Math.random() > 0.5) {
callback.onSuccess("Task completed: " + task);
} else {
callback.onError("Task failed: " + task);
}
} catch (InterruptedException e) {
callback.onError(e.getMessage());
}
}
}
public class CallbackDemo {
public static void main(String[] args) {
AsyncTask task = new AsyncTask();
// Anonymous inner class for callback
task.execute("Download file", new Callback() {
@Override
public void onSuccess(String result) {
System.out.println("✅ Success: " + result);
}
@Override
public void onError(String error) {
System.out.println("❌ Error: " + error);
}
});
// Wait for async task
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Bài tập thực hành
Bài 1: University System
Tạo class University với inner classes:
Department: lưu tên khoa, số lượng giảng viênStudent: lưu thông tin sinh viên, thuộc department nào- Member methods để thêm department, thêm student, hiển thị thông tin
Bài 2: Stack Implementation
Implement Stack<T> với:
- Static nested class
Node<T>để lưu elements - Methods:
push(),pop(),peek(),isEmpty() - Test với các kiểu dữ liệu khác nhau
Bài 3: GUI Event Handling
Tạo class TextField với:
- Method
setOnChangeListener(ChangeListener listener) - Interface
ChangeListenervới methodonChange(String newValue) - Sử dụng anonymous inner class để handle events
- Test với multiple TextFields
Bài 4: Compare Implementations
Implement interface Comparator<Person> bằng:
- Named class
- Anonymous inner class
- Lambda expression
So sánh Person theo: name, age, salary. Chạy và so sánh.
Compiler-generated Class Files
Khi compile, mỗi inner class tạo ra file .class riêng biệt:
Quy tắc đặt tên:
- Member / Static nested:
Outer$InnerName.class - Anonymous class:
Outer$1.class,Outer$2.class,... (đánh số thứ tự) - Local class:
Outer$1LocalName.class
Nếu đề bài hỏi "chương trình tạo ra bao nhiêu .class file", hãy đếm: 1 cho outer class + 1 cho mỗi inner/nested/anonymous/local class.
Local class không thể có static members (trừ static final constants):
void method() {
class LocalClass {
// ❌ Compile error! Local class không thể có static fields
// static int count = 0;
// ✅ OK — static final constant
static final int MAX = 100;
// ❌ Compile error! Local class không thể có static methods
// static void helper() { }
}
}
Lý do: Local class là inner class (non-static context), nên tuân theo cùng quy tắc với member inner class — không có static members.
Ngoại lệ (Java 16+): Từ Java 16, inner classes có thể có static members, nhưng OCP vẫn thường test theo quy tắc trước Java 16.
Tổng kết
- Inner classes giúp organize code và encapsulation tốt hơn
- Member inner class cần outer instance, access được instance members
- Static nested class không cần outer instance, chỉ access static members
- Local inner class chỉ dùng trong method, access final/effectively final locals
- Anonymous inner class dùng cho one-time implementation
- Lambda expressions ngắn gọn hơn anonymous class cho functional interface
Khi nào dùng loại nào?
Member Inner Class
└── Khi cần access instance members của outer class
└── Ví dụ: Engine trong Car, Node trong LinkedList
Static Nested Class
└── Khi không cần access instance members
└── Ví dụ: Builder pattern, Helper classes
Local Inner Class
└── Khi chỉ dùng trong một method
└── Ví dụ: Validators, Comparators trong method
Anonymous Inner Class
└── One-time use, implement interface/extend class
└── Ví dụ: Event handlers, callbacks, threads
Lambda Expression
└── Functional interface, code ngắn gọn
└── Ví dụ: Collections.sort(), Stream API
Inner classes là công cụ mạnh mẽ giúp code organized, encapsulated, và readable!