Chào mừng các bạn đã đến với bài học Java số 38, bài học về Exception (phần tiếp theo). Đây là bài học trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.
Ở bài hôm trước các bạn đã làm quen với khái niệm Exception, và thử tạo một vài Exception để cùng “xem chơi”. Tuy nhiên, cái chính mà Exception ra đời không phải để chúng ta chỉ đứng nhìn một cách “bất lực” như vậy đâu. Chúng ta hoàn toàn có thể đối mặt với Exception, bằng cách bắt lấy chúng, chặn chúng lại không để chương trình bị lỗi, và làm những gì chúng ta mong muốn, như bài trước mình có dùng từ “bẻ luồng”.
Như vậy, phần thứ hai của chuỗi bài về Exception hôm nay chúng ta nói đến các kỹ thuật để bắt các Exception này.
Bạn có thể xem lại kiến thức giới thiệu về Exception ở phần đầu tiên này.
Làm Quen Với Try Catch
Như phần đầu bài học có giới thiệu, hôm nay chúng ta tìm cách bắt Exception, hay một số tài liệu gọi là “bẫy lỗi”.
Và tất nhiên, làm gì cũng vậy, nếu muốn chúng ta làm một điều gì đó, phải cung cấp cho chúng ta công cụ để thực hiện. Công cụ bẫy được cái Exception mà chúng đa đang nói tới có cái tên Try Catch.
Try catch là một công cụ giúp bạn bao bọc lấy đoạn code có khả năng xảy ra Exception. Hoặc các đoạn code đã được báo lỗi bởi hệ thống (chính là các Checked Exception). Các đoạn code mình nói đến này sẽ được chúng ta bao trong khối try. Để rồi nếu quả thực cái Exception đó xảy ra trong khối try đó, thì hệ thống sẽ nhanh chóng “bẻ” luồng logic của ứng dụng, chuyển sang thực thi các đoạn code bên trong khối catch.
Dựa vào lý giải trên đây, mời bạn cùng xem qua cú pháp cho try catch như sau.
try { // Các dòng code có khả năng gây ra Exception } catch (ExceptionClass e) { // Nếu thực sực Exception xảy ra, các dòng code này sẽ được gọi }
Để dễ hiểu hơn, mời bạn cùng try catch thử cho một trường hợp có khả năng xảy ra lỗi mà bài học trước bạn đã biết thông qua bài thực hành sau.
Thực Hành Xây Dựng Try Catch
Chúng ta cùng xem loại đoạn code có khả năng gây ra Exception, bạn có thể xem lại chúng ở mục này. Còn đây mình sẽ viết chúng lại.
int num[] = {1, 2, 3, 4}; System.out.println(num[5]);
Bạn đã biết code đây rơi vào một Unchecked Exception, tức là trình biên dịch sẽ không nhận biết và kiểm tra giúp bạn liệu Exception có xảy ra hay không.
Nhưng nếu trình biên dịch không làm gì cả thì chúng ta cũng đừng đứng khoanh tay nhé. Để chắc chắn ứng dụng của chúng ta “khỏe mạnh”, bạn nên lường trước các tình huống có khả năng xảy ra Exception và bao các đoạn code “có khả năng gây bệnh” vào khối try catch.
Nếu ngoan cố thực thi ứng dụng, bạn sẽ nhận được một thông báo lỗi như thế này. Bạn hãy chú ý cái Exception nhé, chúng ta sẽ catch chúng lại ở bên dưới.
Nào, chúng ta hãy bao đoạn có khả năng gây ra lỗi bằng khối try catch như sau.
try { int num[] = {1, 2, 3, 4}; System.out.println(num[5]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Không thể in được vì không tìm thấy phần tử mảng như mong đợi."); }
Khi này, nếu bạn thực thi lại ứng dụng, người dùng sẽ dễ hiểu hơn với kiểu thông báo lỗi mang “tính người” hơn như sau.
Thực Hành Kiểm Chứng Cách Hoạt Động Của Try Catch
Với bài thực hành này bạn sẽ hiểu rõ hơn thực chất việc “bẫy” lỗi và việc thực hiện các dòng code catch xảy ra ngay như thế nào khi cái “bẫy” đã bẫy được lỗi nhé.
Bạn xem đoạn code với try catch đầy đủ như sau. Hãy thử đoán xem kết quả in ra console sẽ như thế nào.
try { System.out.println("Bài toán thực hiện phép chia:"); int ketQua = 10 / 2; System.out.println("10 chia 2 bằng " + ketQua); ketQua = 10 / 1; System.out.println("10 chia 1 bằng " + ketQua); ketQua = 10 / 0; System.out.println("10 chia 0 bằng " + ketQua); } catch (ArithmeticException e) { System.out.println("Có một phép chia không thực hiện được"); }
Và đây là kết quả.
Ồ. Dòng in ra consolde System.out.println(“10 chia 0 bằng ” + ketQua); đâu rồi nhỉ. Thực ra là, ngay khi hệ thống phát hiện có Exception trong khối try xảy ra, thì các dòng code bên dưới của khối try đó sẽ không được thực thi nữa mà nhường chỗ cho các khối lệnh trong catch thực thi kế tiếp. Như minh họa vui mắt một chút như sau.
Một Số Quy Tắc Với Try Catch
Đến đây thì bạn đã biết cách bao lấy các dòng code có khả năng gây ra Exception bằng try catch như thế nào rồi. Tuy nhiên mình biết với các bạn mới bắt đầu làm quen với try catch sẽ có rất nhiều câu hỏi, mình xin liệt kê chúng dưới dạng các quy tắc sau.
Không Phải Lúc Nào Try Thì Luồng Cũng Bị Bẻ Vào Catch
Như bạn đã làm quen, không phải lúc nào các câu lệnh bên trong khối try cũng gây ra Exception, và vì vậy không phải lúc nào các câu lệnh bên trong khối catch cũng được thực thi đâu nhé, nếu logic trong try được thực thi an toàn. Như ví dụ ngay trên đây, nếu bạn không thực hiện việc chia 10 cho 0 thì sẽ không có câu thông báo “Có một phép chia không thực hiện được” được in ra màn hình.
Nhưng Nếu Bạn Định Nghĩa Try, Bạn Phải Định Nghĩa Catch
Tuy không phải lúc nào các đoạn code trong try cũng gây ra Exception. Nhưng nếu có định nghĩa try, thì bạn buộc phải định nghĩa catch kèm theo. Đó là một sự đảm bảo. Rằng bạn cứ phải luôn chỉ định logic dự phòng cho trường hợp có Exception xảy ra thật.
Catch Với Lớp Exception Luôn Được Không?
Bạn không nhất thiết phải catch với cụ thể các lớp con như ArrayIndexOutOfBoundsException hay ArithmeticException như các ví dụ trên kia. Mà bạn có thể sử dụng lớp cha Exception luôn. Khi đó câu lệnh sẽ là try {…} catch (Exception e) {…}. Vì đâu phải lúc nào bạn cũng nhớ các lớp con của Exception đâu! Nhưng hãy cẩn thận! Hãy học việc sử dụng các Exception con cụ thể, đừng vì lười mà sử dụng luôn lớp Exception, thậm chí là lớp Throwable để thay thế.
Để làm chi ư, dĩ nhiên đầu tiên code của bạn sẽ dễ dàng để quản lý hơn, và việc tung ra lỗi cho người dùng sẽ cụ thể hơn, điều này sẽ được sáng tỏ khi bạn làm quen với việc bắt nhiều lỗi cùng lúc như mục dưới đây.
Try Với Nhiều Catch
Có nhiều khi chúng ta nhồi nhét quá nhiều code vào khối try, khiến nó có khả năng gây ra nhiều hơn một Exception. Thì chúng ta hoàn toàn có thể áp dụng cú pháp sau để có thể bắt nhiều Exception ở cùng một lần try.
try { // Các dòng code có khả năng gây ra Exception } catch (ExceptionClass1 e1) { // Bắt các EceptionClass1 } catch (ExceptionClass2 e2) { // Bắt các EceptionClass2 } catch (ExceptionClass3 e3) { // Bắt các EceptionClass3 }
Theo như cú pháp trên đây, bạn vẫn sẽ bao lấy các câu lệnh có khả năng gây ra Exception vào một khối try. Rồi mỗi catch sẽ là một Exception riêng biệt. Sau đó, với bất cứ dòng nào gây ra Exception, hệ thống sẽ lần lượt tìm đến các khối catch bên dưới try cho đến khi tìm thấy một Exception tương ứng.
Đối với Java 7, bạn còn có thể viết cú pháp try với nhiều catch như sau. Các Exception ở cách viết này được nằm trong cùng một catch thôi nhưng chúng cách nhau bởi ký tự “|“.
try { // Các dòng code có khả năng gây ra Exception } catch (ExceptionClass1|ExceptionClass2|ExceptionClass3 e) { // Bắt các EceptionClass1, EceptionClass2 và EceptionClass3 }
Chúng ta sẽ làm quen với việc xây dựng nhiều catch như thế này ở bài thực hành tiếp theo.
Thực Hành Xây Dựng Try Với Nhiều Catch
Ở bài thực hành này, bạn sẽ được làm quen với các Checked Exception, và làm quen với cách nhanh nhất để thêm khối lệnh try catch (hoặc try với nhiều catch) trong Eclipse.
Đầu tiên, bạn hãy code dòng sau vào một phương thức nào đó.
FileOutputStream outputStream; outputStream = new FileOutputStream("E://file.txt"); outputStream.write(65); outputStream.close();
Bạn sẽ ngay lập tức thấy các báo lỗi từ trình biên dịch.
Đây không hẳn là một lỗi. Vì khi bạn click vào icon hình bóng đèn bên trái, bạn sẽ thấy một gợi ý khắc phục như sau.
Rõ ràng đây là một Checked Exception, và hệ thống đang gợi ý cho bạn với hai lựa chọn.
– Add throws declaration: lựa chọn này cho phép bạn không cần phải tạo ra bất kỳ khối try catch nào, mà tiếp tục “quăng cho thằng nào đó” try catch giúp. Kỹ thuật này sẽ được mình nói đến ở bài tiếp theo.
– Surround with try/catch: đây là lựa chọn mà chúng ta mong muốn, với việc chọn lựa tùy chọn này, hệ thống sẽ bao lấy code của bạn bằng một khối try catch với Exception có tên là FileNotFoundException.
Bạn có thể tự gõ vào try catch như gợi ý. Nhưng nếu bạn chọn tùy chọn thứ hai, hệ thống sẽ try catch tự động cho bạn, tuy nhiên cái tự động này khá “vụng về”, bạn cứ nên sửa lại cho code nhìn đẹp đẹp như sau.
try { FileOutputStream outputStream; outputStream = new FileOutputStream("E://file.txt"); outputStream.write(65); outputStream.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }
Và bạn có thể thấy, dù cho đã try catch với FileNotFoudException, thì hệ thống vẫn cứ đang báo lỗi. Chúng ta cùng xem.
Thì ra là hệ thống cần bạn phải catch thêm một Exception khác có tên IOException. Lúc bấy giờ tùy chọn có thể nhiều hơn. Như hình trên, chúng bao gồm.
– Add throws declaration: giống như mình đã nói ở trên.
– Add catch clause to surrounding try: thêm một catch nữa, dạng try catch catch như cú pháp thứ nhất trong mục này.
– Add exception to existing catch clause: thêm một Exception vào trong catch sẵn có. Dạng try catch (ExceptionClass1 | ExceptionClass2) như cú pháp thứ hai trong mục này.
– Surround with try/catch: lồng thêm một try catch vào try catch hiện tại, cách này mình ít áp dụng vì nhìn code phức tạp lắm.
Đến đây bạn có thể chọn cách thứ hai để code trông như thế này.
try { FileOutputStream outputStream; outputStream = new FileOutputStream("E://file.txt"); outputStream.write(65); outputStream.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
Hoặc chọn cách thứ ba để ra code này.
try { FileOutputStream outputStream; outputStream = new FileOutputStream("E://file.txt"); outputStream.write(65); outputStream.close(); } catch (FileNotFoundException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
Bài Tập Số 1
Giả sử mình có chương trình sau. Chương trình sẽ tạo ra 10 số nguyên ngẫu nhiên và lưu vào mảng 10 phần tử. Chương trình sẽ cho người dùng nhập vào một chỉ số của mảng rồi xuất giá trị của mảng đó ra console.
// Tạo một mảng 10 các số nguyên ngẫu nhiên int randomIntNumbers[] = new int[10]; Random rand = new Random(); for(int i = 0; i < 10; i++) { randomIntNumbers[i] = rand.nextInt(100); } // Cho người dùng nhập một số nguyên và in ra màn hình // phần tử mảng tương ứng Scanner input = new Scanner(System.in); System.out.println("Bạn muốn in ra phần tử mảng thứ mấy? "); int index = input.nextInt(); System.out.println("OK, phần tử mảng thứ " + index + " mang giá trị " + randomIntNumbers[index]);
Tất nhiên, chương trình này tiềm ẩn nhiều nguy cơ gây ra Exception ở runtime. Bạn hãy cố gắng ràng các try catch tương ứng để khi có Exception xảy ra thì chương trình có thể thông báo lỗi kịp thời đến với người dùng nhé.
Và đây là code đáp án, bạn nên tự code và tự “phá” chương trình trước khi click vào đáp án này.
// Tạo một mảng 10 các số nguyên ngẫu nhiên int randomIntNumbers[] = new int[10]; Random rand = new Random(); for(int i = 0; i < 10; i++) { randomIntNumbers[i] = rand.nextInt(100); } try { // Cho người dùng nhập một số nguyên và in ra màn hình // phần tử mảng tương ứng Scanner input = new Scanner(System.in); System.out.println("Bạn muốn in ra phần tử mảng thứ mấy? "); int index = input.nextInt(); System.out.println("OK, phần tử mảng thứ " + index + " mang giá trị " + randomIntNumbers[index]); } catch (InputMismatchException e) { System.out.println("Phần tử mảng chưa hợp lệ, vui lòng nhập vào một số nguyên!"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Phần tử mảng chưa hợp lệ, vui lòng nhập vào giá trị từ 0 đến 9!"); }
Bài Tập Số 2
Mình có đoạn code sau đã được try catch “cẩn thận”, bạn xem try catch như vậy có còn khả năng gây ra lỗi nào nữa không.
Scanner input = new Scanner(System.in); Integer intNumber = null; try { System.out.println("Hãy nhập vào một số nguyên: "); String strNumber = input.nextLine(); intNumber = new Integer(strNumber); } catch (NumberFormatException e) { System.out.println("Vui lòng nhập vào một số nguyên"); } System.out.println("Chuyển thành Hexa: " + Integer.toHexString(intNumber));
Và mình đã phát hiện nó chưa tốt. Vì nếu có Exception xảy ra với đoạn kêu người dùng nhập vào một số, thì intNumber khi đó chưa được khởi tạo (mang giá trị null), và sẽ có một Exception khác xảy ra cho đoạn code cuối cùng. Mình sửa lại code này như sau.
try { Scanner input = new Scanner(System.in); Integer intNumber = null; System.out.println("Hãy nhập vào một số nguyên: "); String strNumber = input.nextLine(); intNumber = new Integer(strNumber); System.out.println("Chuyển thành Hexa: " + Integer.toHexString(intNumber)); } catch (NumberFormatException e) { System.out.println("Vui lòng nhập vào một số nguyên"); }
Các bạn vừa xem xong phần 2 của loạt bài về Exception. Qua bài học này chúng ta đã cùng nhau làm quen với việc bắt lỗi và bẻ luồng của ứng dụng thông qua công cụ try catch. Try catch sẽ còn có “biến thể” nữa và mình sẽ dành phần sau để nói tiếp nhé.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
– Comment bên dưới mỗi bài nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
Bài Kế Tiếp
Chúng ta sẽ nói đến việc sử dụng try catch nhưng có kèm thêm khối finally. Và còn một biến thể nữa của try catch. Các bạn đón xem tiếp nhé.
Cảm ơn bạn nhiều về bài viết nhé <3
cam on ban ve bai viet, that de hieu . Mong ban som viet tiep de moi nguoi cung hoc
Cảm ơn bạn rất nhiều. Đây là bài viết dễ hiểu nhất mà mình từng đọc. Hy vọng bạn sẽ update thêm kiến thức về Java. Luôn ủng hộ blog
Rất rõ ràng và chi tiết, cảm ơn ad nhiều !
Chỗ multi-catch viết gộp thì các Exception phải khác nhau chứ. FileNotFoundException là subclass của IOException thì sao viết gộp được. Viết v không có ý nghĩa gì.
quá hữu ích và thực tế ad à
Đang thắc mắc mà gặp bài viết này giải thích chi tiết quá. Cám ơn bạn nhiều
Chào admin yellowcodebooks
Hiện tại em vừa bắt đầu với java và đang học tới phần này. E chưa hiểu chỗ code trong bài 2 mong ad giải thích giúp em ạ
intNumber = new Integer(strNumber);
hiện tại visual k hỗ trợ lệnh này thì làm sao ạ
Chào bạn, câu lệnh intNumber = new Integer(strNumber) sẽ làm thao tác chuyển đổi kiểu dữ liệu, từ biến strNumber đang chứa dữ liệu kiểu String, sang biến intNumber với kiểu int. Thực ra bài thực hành trên mình dùng rối lên một tí để cho nó tung ra các Exception thôi bạn. Nếu thực sự không cần thiết bạn cũng không cần phải đọc vào một strNumber rồi chuyển nó sang intNumber như bài tập.
Còn việc Visual không hỗ trợ thì mình cũng thấy hơi lạ, do mình cũng không sử dụng Visual để lập trình Java nên cũng không rõ lắm, bạn có thể chuyển sang dùng Eclipse hoặc InteliJ được không.
Exception con này sao ad biết vậy? nó có thư viện tổng hợp đúng không?
Hihi mình vẫn chưa hiểu rõ bạn muốn hỏi gì, nếu bạn muốn hiểu rõ hơn Exception này thì có thể chat với mình thông qua Facebook nhé.
catch (InputMismatchException e)
catch (IOException e)
làm sao mình biết catch nó thuộc lớp nào vậy ad, có cách nào để mình năm bắt không a!