finally luôn chạy
Vấn đề
Phần tiêu đề “Vấn đề”Khối finally được thiết kế để luôn luôn chạy, kể cả khi try hoặc catch có câu lệnh return - điều này có thể tạo ra hành vi bất ngờ nếu finally cũng có return:
static int test() { try { return 1; } finally { return 2; // GHI ĐÈ giá trị return của try! }}
System.out.println(test()); // 2 - KHÔNG phải 1!Vì sao xảy ra?
Phần tiêu đề “Vì sao xảy ra?”Khi try gặp return 1, Java không thoát ngay lập tức - nó ghi nhớ giá trị 1 sẽ trả về, rồi chạy khối finally trước khi thực sự thoát khỏi phương thức. Nếu finally cũng có return, giá trị mới trong finally sẽ ghi đè hoàn toàn giá trị đã ghi nhớ từ try - đây gần như luôn là một lỗi logic không mong muốn.
Tương tự với break/continue trong finally khi vòng lặp chứa try/finally.
Trường hợp tinh vi hơn: sửa biến trong finally
Phần tiêu đề “Trường hợp tinh vi hơn: sửa biến trong finally”static int testValue() { int value = 1; try { return value; // giá trị 1 được "chốt lại" NGAY tại đây (kiểu nguyên thủy được sao chép) } finally { value = 2; // KHÔNG ảnh hưởng gì đến giá trị đã return ở trên }}
System.out.println(testValue()); // 1 - value đã được sao chép lúc return, finally sửa value không còn tác dụng⚠️ Khác với ví dụ đầu tiên (finally có return riêng ghi đè hoàn toàn), ở đây finally chỉ sửa biến, không tự return - nên giá trị trả về vẫn giữ nguyên từ try vì kiểu nguyên thủy đã được sao chép giá trị tại thời điểm return.
Giải pháp
Phần tiêu đề “Giải pháp”Không bao giờ đặt return, break, hay continue bên trong khối finally - đây được coi là một thực hành xấu (bad practice) rõ ràng, nhiều công cụ phân tích tĩnh (linter) sẽ cảnh báo:
static int test() { int result; try { result = 1; } finally { System.out.println("Dọn dẹp tài nguyên"); // CHỈ dùng finally để dọn dẹp, không return ở đây } return result; // return NGOÀI try/finally - rõ ràng, dễ hiểu}finally nên chỉ dùng để dọn dẹp tài nguyên (đóng file, đóng kết nối) - hoặc tốt hơn nữa, dùng try-with-resources (xem trang “Làm việc với file”) để Java tự động lo việc đóng tài nguyên mà không cần viết finally thủ công.
Tóm tắt
Phần tiêu đề “Tóm tắt”
finallyluôn chạy, kể cả saureturn/break/continuetrongtry/catch- nếufinallycũng córeturn, nó sẽ ghi đè giá trị trả về ban đầu.
- Không bao giờ đặt
return/break/continuetrongfinally- Chỉ dùng
finallyđể dọn dẹp tài nguyên; ưu tiên try-with-resources khi có thể