1. Lớp cục bộ
Lớp cục bộ — là lớp được khai báo bên trong một phương thức (hoặc thậm chí trong một khối mã, ví dụ trong vòng lặp hoặc trong khối if). Nó chỉ hiển thị và truy cập được bên trong phương thức này, còn bên ngoài — coi như không tồn tại. Khác với lớp ẩn danh, lớp cục bộ có tên (dù đơn giản), có thể chứa nhiều phương thức và thậm chí cả các trường.
Nếu cần một phép so sánh — hãy tưởng tượng bạn đang nấu một món ăn phức tạp theo công thức. Ở một bước, bạn cần làm một loại sốt đặc biệt chỉ dùng cho công thức này và không ở đâu khác. Thay vì xuất bản cả một cuốn sách công thức chỉ vì một loại sốt, bạn mô tả cách làm sốt ngay trong công thức chính. Lớp cục bộ hoạt động đúng như vậy — đó là “công thức trong công thức”, chỉ cần thiết trong một phương thức cụ thể.
Ví dụ khai báo lớp cục bộ
public class Outer {
void someMethod() {
class Local {
void print() {
System.out.println("Hello from Local!");
}
}
Local local = new Local();
local.print();
}
}
Ở đây lớp Local được khai báo bên trong phương thức someMethod của lớp Outer. Nó chỉ tồn tại trong phương thức này và không nhìn thấy từ bên ngoài.
Cú pháp của lớp cục bộ
Khai báo lớp cục bộ giống khai báo lớp thông thường, chỉ khác là nó diễn ra bên trong phương thức:
void myMethod() {
class MyLocalClass {
void doSomething() {
System.out.println("Lớp cục bộ đang hoạt động!");
}
}
MyLocalClass obj = new MyLocalClass();
obj.doSomething();
}
Đặc điểm cú pháp:
- Lớp cục bộ có thể được khai báo bên trong bất kỳ phương thức, constructor hoặc thậm chí khối khởi tạo nào.
- Tên lớp cục bộ là bắt buộc (khác với lớp ẩn danh).
- Lớp cục bộ không thể được khai báo với các bộ sửa đổi truy cập (public, private, protected) hoặc với bộ sửa đổi static.
- Lớp cục bộ có thể triển khai các interface hoặc kế thừa từ các lớp khác.
So sánh các loại lớp:
public class Example {
// 1. Lớp thông thường (trong tệp riêng)
// class RegularClass { ... }
// 2. Lớp inner (bên trong lớp)
class InnerClass { }
// 3. Lớp lồng nhau tĩnh (bên trong lớp)
static class NestedClass { }
void method() {
// 4. Lớp cục bộ (trong phương thức)
class LocalClass { }
// 5. Lớp ẩn danh (trong phương thức, không tên)
Object obj = new Object() { };
}
}
Lớp cục bộ = lớp có tên chỉ dành cho phương thức này!
2. Đặc điểm của lớp cục bộ
Truy cập biến của phương thức
Lớp cục bộ có thể truy cập:
- Các trường của lớp bao ngoài (ngay cả khi chúng private).
- Chỉ các biến final hoặc effectively final của phương thức bao quanh.
“Effectively final” là gì?
Đó là biến có giá trị không thay đổi sau khi khởi tạo. Ví dụ:
void foo() {
int x = 5; // effectively final
class L { void print() { System.out.println(x); } }
}
Nếu sau đó bạn viết x = 10;, trình biên dịch sẽ báo lỗi.
Vì sao lại như vậy?
Điều này liên quan đến cách Java hiện thực lớp cục bộ “bên dưới nắp capo”: biến của phương thức thực tế được sao chép vào bên trong lớp cục bộ, chứ không phải lưu bằng tham chiếu. Nếu biến có thể thay đổi, lớp cục bộ có thể làm việc với bản sao đã lỗi thời — dẫn tới lỗi và đau đầu.
Ví dụ về truy cập biến
public class Outer {
private String secret = "bí mật!";
void revealSecret() {
String greeting = "Xin chào,"; // effectively final
class Revealer {
void printSecret() {
System.out.println(greeting + " " + secret);
}
}
Revealer revealer = new Revealer();
revealer.printSecret();
}
}
3. Ứng dụng của lớp cục bộ: để làm gì và khi nào?
Lớp cục bộ không phải công cụ dùng hằng ngày, nhưng đôi khi chúng giúp mã sạch và gọn hơn. Dưới đây là khi nên dùng:
- Logic hỗ trợ, chỉ đặc thù cho một phương thức.
Ví dụ, nếu trong phương thức cần hiện thực sắp xếp phức tạp, lọc hoặc một cấu trúc dữ liệu tạm thời. - Che giấu chi tiết để không “làm bẩn” lớp bằng các kiểu thừa.
Nếu lớp chỉ cần ở một nơi, tại sao phải làm cho nó hiển thị trong toàn bộ lớp hoặc gói? - Hiện thực các mẫu thiết kế khi các đối tượng hỗ trợ chỉ cần cục bộ.
Ví dụ: sắp xếp bằng lớp cục bộ
Giả sử bạn có danh sách tên và muốn sắp xếp theo độ dài. Có thể tạo một lớp cục bộ làm Comparator:
import java.util.*;
public class NameSorter {
public void sortNames(List<String> names) {
class LengthComparator implements Comparator<String> {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
}
Collections.sort(names, new LengthComparator());
}
}
Ví dụ: cấu trúc tạm thời
Đôi khi cần tạo cấu trúc hỗ trợ để xử lý dữ liệu trong một phương thức:
void processScores(int[] scores) {
class ScoreInfo {
int score;
boolean passed;
ScoreInfo(int score) {
this.score = score;
this.passed = score >= 60;
}
}
for (int s : scores) {
ScoreInfo info = new ScoreInfo(s);
System.out.println("Điểm: " + info.score + ", đỗ: " + info.passed);
}
}
4. Lớp cục bộ vs. lớp ẩn danh
Đôi khi nảy sinh câu hỏi: cần gì lớp cục bộ khi đã có lớp ẩn danh? Hãy cùng tìm hiểu.
- Lớp cục bộ — là lớp có tên, có thể dùng nhiều lần trong một phương thức, thêm trường, nhiều phương thức, lớp lồng nhau.
- Lớp ẩn danh — là hiện thực dùng một lần của một interface hoặc lớp cha, không có tên, thường chỉ với một phương thức (hoặc ghi đè một hai phương thức).
Khi nào dùng:
- Nếu logic đơn giản và chỉ cần một lần — hãy dùng lớp ẩn danh.
- Nếu cần nhiều phương thức hoặc trường hơn, hoặc lớp sẽ được dùng ở nhiều chỗ trong phương thức — hãy dùng lớp cục bộ.
Ví dụ so sánh
Lớp ẩn danh:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Nhanh và ẩn danh!");
}
};
Lớp cục bộ:
void doWork() {
class MyWorker implements Runnable {
@Override
public void run() {
System.out.println("Tôi là lớp cục bộ!");
}
}
MyWorker worker = new MyWorker();
worker.run();
}
5. Hạn chế và đặc điểm của lớp cục bộ
- Bộ sửa đổi truy cập: Lớp cục bộ không thể được khai báo là public, private hoặc protected. Cũng không thể thêm static.
- Thành viên tĩnh: Lớp cục bộ không thể chứa thành viên tĩnh, ngoại trừ hằng số (static final).
- Phạm vi hiển thị: Lớp cục bộ chỉ hiển thị trong khối nơi nó được khai báo.
- Sử dụng tham số generic: Lớp cục bộ có thể là generic nếu cần.
- Lồng nhau: Có thể khai báo lớp cục bộ bên trong các lớp cục bộ khác (nhưng đừng lạm dụng, nếu không mã sẽ trông như búp bê Matryoshka).
6. Lớp cục bộ trong ứng dụng thực tế
Hãy tiếp tục phát triển ứng dụng học tập của chúng ta. Giả sử có lớp Quiz, lớp này đặt câu hỏi cho người dùng và kiểm tra câu trả lời. Chúng ta muốn bên trong phương thức kiểm tra tạo một lớp tạm thời để lưu câu trả lời của người dùng và trạng thái kiểm tra.
Ví dụ: sử dụng lớp cục bộ trong ứng dụng
import java.util.Scanner;
public class Quiz {
private String question = "Thủ đô của Pháp?";
private String correctAnswer = "Paris";
public void runQuiz() {
Scanner scanner = new Scanner(System.in);
System.out.println(question);
String userAnswer = scanner.nextLine();
// Lớp cục bộ để lưu kết quả
class AnswerResult {
String answer;
boolean isCorrect;
AnswerResult(String answer) {
this.answer = answer;
this.isCorrect = answer.equalsIgnoreCase(correctAnswer);
}
void printResult() {
if (isCorrect) {
System.out.println("Đúng!");
} else {
System.out.println("Sai, đáp án đúng: " + correctAnswer);
}
}
}
AnswerResult result = new AnswerResult(userAnswer);
result.printResult();
}
}
Ở đây lớp AnswerResult chỉ tồn tại bên trong phương thức runQuiz và không cần ở nơi nào khác. Đây là một ví dụ điển hình về lớp cục bộ trong thực tế!
7. Lỗi thường gặp khi làm việc với lớp cục bộ
Lỗi số 1: cố truy cập biến của phương thức không phải final hoặc không effectively final.
Nếu bạn khai báo một biến trong phương thức rồi thay đổi giá trị của nó sau khi dùng trong lớp cục bộ, trình biên dịch sẽ báo lỗi ngay. Hãy luôn đảm bảo những biến như vậy không thay đổi sau khi khởi tạo.
Lỗi số 2: muốn thêm các bộ sửa đổi truy cập hoặc static.
Lớp cục bộ không thể được khai báo với các bộ sửa đổi public, private, protected hoặc static. Nếu bạn cố làm vậy — trình biên dịch sẽ không chấp nhận.
Lỗi số 3: cố dùng lớp cục bộ bên ngoài phương thức.
Lớp cục bộ chỉ “sống” trong phương thức (hoặc khối) nơi nó được khai báo. Từ các phương thức khác hoặc bên ngoài lớp, nó không thể truy cập.
Lỗi số 4: lạm dụng lớp cục bộ cho logic phức tạp.
Nếu lớp cục bộ của bạn trở nên quá lớn, chứa nhiều phương thức hoặc trường — rất có thể bạn nên tách nó thành một lớp riêng. Lớp cục bộ phù hợp cho các nhiệm vụ nhỏ gọn, hỗ trợ.
GO TO FULL VERSION