1. Giới thiệu
Trong lập trình thường có những tình huống khi một phương thức gặp lỗi nhưng không biết cách xử lý cho đúng. Ví dụ, phương thức đọc một tệp nhưng không biết phải làm gì nếu tệp không tồn tại: hỏi người dùng? Kết thúc chương trình? Thử một tệp khác? Trong những trường hợp như vậy, phương thức có thể “chuyển trách nhiệm” cho bên gọi — tức là “chuyển tiếp” ngoại lệ tiếp tục theo chuỗi lời gọi.
Chuyển tiếp ngoại lệ là cơ chế cho phép một phương thức không tự xử lý lỗi, mà thông báo cho mã gọi: “Tôi có vấn đề ở đây, hãy tự xử lý!”
Tương tự: Hãy tưởng tượng bạn là nhân viên tổng đài. Khách gọi đến với một câu hỏi mà bạn không biết câu trả lời. Thay vì đoán mò, bạn nói: “Xin chờ một chút, tôi sẽ nối máy với chuyên gia.” Bạn “chuyển tiếp” câu hỏi đi tiếp.
Từ khóa throws: hoạt động như thế nào
Trong Java, để chuyển tiếp ngoại lệ sử dụng từ khóa throws trong khai báo phương thức.
Cú pháp:
tip_vozvrashchaemogo_znacheniya imyaMetoda(...) throws ExceptionType
{
// mã của phương thức
}
Sau throws là kiểu ngoại lệ có thể phát sinh trong phương thức này. Đây giống như lời cảnh báo cho các lập trình viên khác: “Chú ý! Phương thức này có thể ném ngoại lệ kiểu này. Hãy sẵn sàng!”
Ví dụ:
public void readFile(String filename) throws FileNotFoundException
{
FileReader reader = new FileReader(filename); // có thể ném FileNotFoundException
// ...
}
Ở đây phương thức readFile không tự xử lý lỗi, mà thông báo: “Tôi có thể ném FileNotFoundException — hãy để người gọi tôi quyết định phải làm gì”.
2. Phương thức gọi nên phản ứng thế nào?
Nếu bạn gọi một phương thức được khai báo với throws, bạn có hai lựa chọn:
- Xử lý ngoại lệ bằng try-catch
- Cũng chuyển tiếp ngoại lệ đi tiếp (thêm throws vào phương thức của bạn)
Lựa chọn 1: xử lý qua try-catch
public static void main(String[] args)
{
try
{
readFile("data.txt");
}
catch (FileNotFoundException e)
{
System.out.println("Không tìm thấy tệp: " + e.getMessage());
}
}
Ở đây chúng ta bắt ngoại lệ và tự quyết định phải làm gì (ví dụ, hiển thị thông báo cho người dùng).
Lựa chọn 2: chuyển tiếp lên trên
public static void main(String[] args) throws FileNotFoundException
{
readFile("data.txt");
}
Bây giờ trách nhiệm xử lý lỗi thuộc về bên gọi main (thông thường là chính JVM — nếu lỗi không được xử lý, chương trình sẽ kết thúc với thông báo lỗi).
3. Ví dụ: đọc tệp với chuyển tiếp ngoại lệ
Ví dụ đầy đủ với IOException và xử lý ở cấp độ main:
import java.io.*;
public class FileDemo
{
// Phương thức khai báo rằng có thể ném ra IOException
public static void printFirstLine(String filename) throws IOException
{
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line = reader.readLine();
System.out.println("Dòng đầu tiên: " + line);
reader.close();
}
public static void main(String[] args)
{
try
{
printFirstLine("nofile.txt");
}
catch (IOException e)
{
System.out.println("Lỗi khi đọc tệp: " + e.getMessage());
}
}
}
Phương thức printFirstLine không biết phải làm gì nếu tệp không tồn tại — nó chỉ chuyển tiếp ngoại lệ. Trong main chúng ta bắt lỗi và in ra thông báo.
4. Những điểm hữu ích
Khi nào và tại sao nên dùng chuyển tiếp ngoại lệ?
- Khi phương thức không thể hoặc không nên quyết định cách xử lý lỗi (ví dụ, mã thư viện).
- Khi việc xử lý lỗi phụ thuộc vào ngữ cảnh (trường hợp này kết thúc chương trình, trường hợp khác — thử tệp khác).
- Để không làm rối mã với các khối try-catch không cần thiết.
Best practice: Hãy chuyển tiếp ngoại lệ nếu bạn không thể xử lý chúng một cách có ý nghĩa. Đừng bắt ngoại lệ chỉ để cho có!
Có thể chuyển tiếp nhiều ngoại lệ
public void process() throws IOException, SQLException
{
// ...
}
Ngoại lệ checked và unchecked: nhắc lại
Checked exceptions (ví dụ, IOException, SQLException) — trình biên dịch yêu cầu phải xử lý hoặc chuyển tiếp chúng.
Unchecked exceptions (ví dụ, NullPointerException, IllegalArgumentException) — trình biên dịch không yêu cầu xử lý, bạn có thể không nêu trong throws.
5. Những lỗi thường gặp khi chuyển tiếp ngoại lệ
Lỗi số 1: quên khai báo throws cho ngoại lệ checked.
Nếu một phương thức có thể ném ngoại lệ checked, nhưng bạn không khai báo nó trong throws và cũng không xử lý qua try-catch — trình biên dịch sẽ báo lỗi.
Lỗi số 2: bắt ngoại lệ nhưng không xử lý nó.
Viết catch (Exception e) {} — là thực hành tệ! Tốt hơn hãy chuyển tiếp ngoại lệ nếu bạn không biết phải làm gì với nó.
Lỗi số 3: chuyển tiếp kiểu quá tổng quát.
Nếu phương thức chỉ có thể ném FileNotFoundException, đừng viết throws Exception — điều đó làm khó việc hiểu mã.
Lỗi số 4: đưa các ngoại lệ unchecked vào throws.
Không có ý nghĩa gì khi viết throws NullPointerException — trình biên dịch không yêu cầu điều đó và nó cũng không giúp ích cho việc xử lý lỗi.
GO TO FULL VERSION