Java Bài 40: Exception Tập 4 – Throw, Throws & Custom Exception

Posted by

Chào mừng các bạn đã đến với bài học Java số 40, 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.

Mình thông báo cho các bạn biết rằng đây là bài viết cuối cùng của mình về “series” Exception này. Như vậy nhìn lại chúng ta có các bài viết về Exception như sau.

Làm quen với Exception – Đây là bài học mở đầu về Exception. Bài học giúp cho bạn bắt đầu tiếp cận với khái niệm LỖI trong Java. Từ đó hiểu rõ thế nào là Exception. Ngoài ra bài học còn giúp bạn phân biệt các loại Exception trong hệ thống Java nữa.
Bắt (bẫy) Exception – Sau khi đã hiểu Exception là gì, thì bạn sẽ biết được cách thức bắt, hay bẫy Exception. Biết được kiến thức này, bạn có thể “bẻ luồng” logic của chương trình thông qua công cụ try catch. Việc bẻ luồng này nhằm mục đích vẫn đảm bảo sao cho ứng dụng của bạn vẫn luôn luôn “sống” dù cho nó có bị các LỖI nào tác động đến đi chăng nữa.
Tìm hiểu sâu hơn về try catch – Khi bạn đã biết bắt Exception thông qua try catch, bạn sẽ được biết đến công dụng cao hơn nữa của công cụ này qua hai khái niệm Try Catch với FinallyTry Catch với Resource. Và mình cũng tranh thủ nói đến một số phương thức hữu ích của lớp Exception ở bài này, giúp bạn có thể hiểu rõ hơn về cách sử dụng công cụ này, và có thể vận dụng các phương thức đó cho bài học ngày hôm nay.

Bài hôm nay chúng ta đến với kiến thức còn lại của Exception. Bài học sẽ hướng dẫn bạn đạt đến một cảnh giới hoàn toàn không sợ sệt gì với Exception cả. Thậm chí bạn có thể “tung hứng” chúng, đẩy chúng về một nơi khác xử lý, và rồi bạn còn có thể tự tạo ra một Exception cho riêng bạn nữa.

Nếu bạn thấy thú vị thì mời các bạn cùng đến với bài viết.

Throw – Tự Tung Ra Một Exception

Tất cả chúng ta cho đến giờ phút này đều hiểu rõ về Exception rồi. Nhưng chúng ta vẫn chỉ đang “chờ đón” Exception đến một cách thụ động mà thôi. Tức là chúng ta chỉ mới đang try và chờ đón (thậm chí không mong muốn) catch ra lỗi.

Nhưng đôi khi trong thực tế, có một số trường hợp buộc chúng ta muốn nhanh chóng hơn tung ra một Exception. Mình dùng từ “tung ra” bởi chính ở nghĩa của từ throw: ném, quăng, tung. Exception được tung ra này có thể là Checked hay Unchecked Exception. Và việc tung ra một Exception này là do chủ ý của bạn.

Cách sử dụng throw cũng khá là đơn giản. Bất cứ nơi nào đó bên trong một phương thức, hay một khối lệnh, mà bạn muốn tung ra Exception, thì cứ dùng throw như với bài thực hành sau.

Thực Hành Sử Dụng Throw Để Tung Ra Một Exception

Chúng ta bắt đầu với việc thiết kế một chương trình cho phép người dùng nhập vào tuổi của nhân viên, rồi in ra tuổi vừa mới nhập.

Yêu cầu của chương trình tưởng như vô cùng đơn giản, thế nhưng như bạn biết, nếu không khéo, ứng dụng có thể sẽ bị “crash” nếu như người dùng cố tình nhập vào một dữ liệu tuổi không hợp lệ.

Với bài thực hành này chúng ta thử thiết kế riêng một phương thức nhập tuổi như sau.

private static int nhapTuoiNhanVien() {
	Scanner scanner = new Scanner(System.in);
	System.out.print("Hãy nhập tuổi nhân viên: ");
	int tuoi = scanner.nextInt();
	return tuoi;
}

Bạn có thể thấy, phương thức nhapTuoiNhanVien() này có kiểu trả vềint (trả về tuổi vừa mới nhập vào). Và phương thức này còn được khai báo với từ khóa static, để lát nữa chúng ta có thể gọi đến từ phương thức main() (vốn là một phương thức static). Để hiểu rõ tại sao nhapTuoiNhanVien() lại phải khai báo static như vậy thì bạn có thể xem lại bài học này nhé.

Tiếp theo, ở phương thức main(), để đảm bảo ứng dụng không bị crash, chúng ta nên bao bọc lời gọi đến nhapTuoiNhanVien() vào bên trong một try catch hợp lý.

public static void main(String[] args) {
	try {
		int tuoi = nhapTuoiNhanVien();
		System.out.println("Tuổi đã nhập: " + tuoi);
	} catch (InputMismatchException e) {
		System.out.println("Tuổi nhập vào chưa hợp lệ. Lỗi: " + e.toString());
	}
}

Từ các bài học trước, bạn có thể dễ dàng đoán được rằng nếu nhập vào một tuổi 28 chẳng hạn, ứng dụng chạy tốt. Nhưng nếu người dùng cắc cớ nhập vào 28a, rất nhanh chóng ứng dụng sẽ thông báo lỗi ra cho người dùng ngay! Ôi mình yêu try catch biết bao nhiêu.

Exception - Bắt lỗi nhập vào không phải số

Thế nhưng nếu người dùng vẫn cắc cớ, nhập vào một tuổi mang giá trị âm, -5 chẳng hạn. Ứng dụng của chúng ta vẫn hơi “ngu” khi để lọt một sơ hở này.

Exception - Ví dụ để lọt một giá trị âm cho tuổi

Làm gì có cái tuổi như thế này! Vậy để nâng cấp cho cái sự thông minh của ứng dụng, chúng ta sẽ tận dụng từ khóa throw. Như mình có nói, từ khóa này sẽ được tận dụng để chúng ta có thể tung ra một Exception bất cứ lúc nào chúng ta muốn, trong trường hợp này chúng ta nên tung ra một Exception khi mà người dùng nhập vào một tuổi âm. Bạn có thể xem mình code thêm vào phương thức nhapTuoiNhanVien() như sau.

private static int nhapTuoiNhanVien() {
	Scanner scanner = new Scanner(System.in);
	System.out.print("Hãy nhập tuổi nhân viên: ");
	int tuoi = scanner.nextInt();
	if (tuoi < 0) throw new InputMismatchException("tuổi không được nhỏ hơn 0.");
	return tuoi;
}

Bạn có thể thấy code bổ sung được tô sáng như trên. Mình giải thích tí xíu. Bên trong nhapTuoiNhanVien(), một khi kiểm tra thấy tuổi do người dùng nhập vào nhỏ hơn 0, ứng dụng sẽ tung ra (throw) bất kỳ một Exception nào, trong ví dụ này InputMismatchException được tung ra. Và bạn có thể hiểu bạn có thể tung ra bất cứ Exception nào bạn muốn. Bên cạnh việc tung ra một Exception, bạn có thể định nghĩa ra một thông báo kiểu String và truyền vào constructor của Exception như code trên nữa. Sau đó ở nơi gọi đến nhapTuoiNhanVien(), nếu nơi đó có try catch hợp lý, và gặp đúng điều kiện mà Exception được tung ra (tuổi nhập vào nhỏ hơn 0) thì như các bài học trước, logic của ứng dụng sẽ bị bẻ về khối catch.

Giờ đây, với sự nâng cấp này, nếu người dùng nhập vào một giá trị âm, bạn xem.

Exception - Tung ra một lỗi

Qua bài thực hành này thì bạn đã hiểu cách dùng throw rồi đúng không nào.

Trước khi qua mục tiếp theo, mình có một lưu ý rằng. Với ví dụ trên đây, khi tung ra một Exception, chúng ta chọn InputMismatchException. Đây là một Unchecked Exception. Do đó nơi gọi đến phương thức có tung Exception này không bị trình biên dịch báo lỗi bắt bạn phải thêm vào một try catch. Nhưng nếu bạn thử nghiệm với một Checked Exception xem, khi đó sẽ có nhiều điều đáng nói đến, và còn liên quan đến khái niệm throws nữa nên mình sẽ dành trường hợp này và nói chung ở mục kế tiếp sau.

Throws – Đẩy Exception Cho Nơi Khác Xử Lý

Bạn chú ý đừng nhầm lẫn nhé. Mục trên kia có nói đến việc tự ý tung ra một Exception thông qua từ khóa throw. Còn mục này đang nói đến throws (có thêm chữ s).

Khác với throw rằng bạn có thể sử dụng bên trong một phương thức hay một khối lệnh nào đó. Throws lại dùng ngay khi bạn khai báo một phương thức.

Throws được dùng khi bạn không muốn phải xây dựng try catch bên trong một phương thức nào đó, bạn “đẩy trách nhiệm” phải try catch này cho phương thức nào đó bên ngoài có gọi đến nó phải try catch giúp cho bạn.

Chúng ta cùng đến với các bài thực hành sau để hiểu rõ nhất về throws, và về cả việc vận dụng tốt với nhau giữa throwthrows nữa nhé.

Bài Thực Hành Số 1: Sử Dụng Throw Để Tung Ra Một Checked Exception

Bạn đang thắc mắc tại sao đang nói về throws mà tiêu đề lại là thực hành với throw?

Bạn cứ thử qua các bước sau đây sẽ rõ thực hư. Mình sẽ lấy lại source code của bài thực hành trên kia. Trong phương thức nhapTuoiNhanVien(), chúng ta thử thay thế việc throw một InputMismatchException, bằng một Checked Exception nào đó xem sao. Mình sẽ thử với IOException. Code của phương thức này sẽ như sau.

private static int nhapTuoiNhanVien() {
	Scanner scanner = new Scanner(System.in);
	System.out.print("Hãy nhập tuổi nhân viên: ");
	int tuoi = scanner.nextInt();
	if (tuoi < 0) throw new IOException("tuổi không được nhỏ hơn 0.");
	return tuoi;
}

Sau khi tung ra một Checked Exception như trên, bạn dễ dàng nhận thấy hệ thống sẽ yêu cầu bạn phải làm một trong hai việc như sau.

Exception - Lựa chọn throws

Lựa chọn đầu tiên Add throws declaration sẽ khai báo một throws cho phương thức nhapTuoiNhanVien() này. Và như mình có nói, nếu phương thức đã khai báo throws, nó không cần phải try catch gì nữa, mà có thể đẩy trách nhiệm try catch này cho phương thức nào đó khác. Lựa chọn thứ hai Surround with try/catch thì bạn cũng biết từ các bài học trước rồi, nó sẽ giúp bao đoạn code này lại bằng khối lệnh try catch. Chúng ta nên chọn lựa chọn đầu tiên trong tình huống này, vừa đúng với yêu cầu bài học, vừa giúp dễ quản lý code nữa, vì bạn vừa tung ra một Exception mà lại try catch nó ngay, thì trông hơi bị dở người tí.

Với lựa chọn đầu tiên, code của chúng ta sẽ như sau.

private static int nhapTuoiNhanVien() throws IOException {
	Scanner scanner = new Scanner(System.in);
	System.out.print("Hãy nhập tuổi nhân viên: ");
	int tuoi = scanner.nextInt();
	if (tuoi < 0) throw new IOException("tuổi không được nhỏ hơn 0.");
	return tuoi;
}

Với việc đẩy trách nhiệm phải try catch Checked Exception này, thì phương thức gọi đến nhapTuoiNhanVien() sẽ báo lỗi quen thuộc như sau.

Exception - Try Catch với Exception đã throws

Mình cá là các bạn đã biết cách làm gì rồi. Và đây, mình đã sửa lại theo ý các bạn, try catch với IOException.

public static void main(String[] args) {
	try {
		int tuoi = nhapTuoiNhanVien();
		System.out.println("Tuổi đã nhập: " + tuoi);
	} catch (IOException e) {
		System.out.println("Tuổi nhập vào chưa hợp lệ. Lỗi: " + e.toString());
	}
}

Bạn đã hiểu cách thức dùng đến throws để đẩy trách nhiệm try catch đi chỗ khác chưa nào.

Bài Thực Hành Số 2: Sử Dụng Throws Để Đẩy Exception Đi Nơi Khác

Với bài thực hành trên kia có lẽ bạn cũng đã hiểu rõ cách dùng throws rồi. Tuy nhiên mình muốn các bạn làm thêm một ví dụ nữa để chỉ tập trung vào một mình throws thôi, không có throw xuất hiện trong này.

Chúng ta cùng quay lại xây dựng tình huống try catch như bài thực hành này của bài trước. Chỉ khác một chỗ lần này chúng ta xây dựng riêng một phương thức ghi file và trong phương thức này không hề có một try catch nào cả.

Nào, bắt đầu với việc xây dựng phương thức ghi file (code ghi file giống bài thực hành hôm trước thôi).

private static void ghiFile() {
	FileOutputStream outputStream;
	outputStream = new FileOutputStream("E://file.txt");
	outputStream.write(65);
	outputStream.close();
}

Chúng ta lại sẽ nhận được thông báo lỗi.

Exception - Báo lỗi chưa catch

Khi click vào icon cái bóng đèn bên trái, bạn đã biết là nên chọn tùy chọn này.

Exception - Chọn throws

Và khi đã throws đầy đủ, phương thức này sẽ trông như thế này đây.

private static void ghiFile() throws FileNotFoundException, IOException {
	FileOutputStream outputStream;
	outputStream = new FileOutputStream("E://file.txt");
	outputStream.write(65);
	outputStream.close();
}

Cuối cùng, hiển nhiên ở nơi gọi đến phương thức này sẽ buộc phải try catch đầy đủ, hoặc throws tiếp cho phương thức nào đó kế tiếp.

public static void main(String[] args) {
	try {
		ghiFile();
	} catch (FileNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

Tự Tạo Một Exception Cho Riêng Bạn

Đến bước này đây, tất cả chúng ta đều đã đạt đến đỉnh cao nhất của việc hiểu và sử dụng Exception rồi. Khi mà tất cả chúng ta rồi đây sẽ tự tạo ra một Exception cho riêng chúng ta.

Việc xây dựng một Exception cho riêng chúng ta sẽ được gọi với một cái tên ngắn thôi: Custom Exception. Custom Exception thực chất cũng là một Exception nào đó, nó được đặt một cái tên hợp lý hơn trong hoàn cảnh ứng dụng của bạn (thay vì các cái tên Exception có sẵn mà bạn đã từng làm quen). Rồi thông qua Custom Exception, bạn có thể điều chỉnh việc thông báo lỗi, để mang đến một sự rõ ràng hơn cho ứng dụng.

Trước khi chính thức tạo một Custom Exception, chúng ta cùng điểm qua mấy ý quan trọng này trước.

– Tất cả các Custom Exception phải đều là con của lớp Throwable (bạn có thể xem lại kiến thức ở mục này nếu lỡ quên lớp Throwable là lớp gì nha).
– Nếu bạn muốn tạo ra một Custom Exception và muốn hệ thống báo lỗi khi không try catch cho nó, thì hãy tạo một lớp và kế thừa từ lớp Exception (có thể xem ở link trên để biết lớp Exception là lớp gì). Và dĩ nhiên lớp Exception là con của Throwable rồi nên nếu bạn làm theo ý này thì cũng sẽ thỏa ý tên kia thôi.
– Còn nếu bạn muốn tạo một Custom Exception và không cần hệ thống báo lỗi, bạn chỉ cần tạo một lớp và kế thừa từ lớp RuntimeException mà thôi (RuntimeException cũng là con của Throwable, và link trên cũng đã nói về lớp này).

Các ý trên đây cỏn con thôi đúng không nào. Chúng ta cùng đến với bài thực hành.

Thực Hành Tự Tạo Một Exception Kiểm Tra Tuổi

Chúng ta sẽ làm lại một chương trình nhập vào tuổi nhân viên và kiểm tra tuổi như trên kia. Nhưng thay vì dùng IOException để kiểm tra và tung ra một lỗi, trông chẳng ăn nhập gì, chúng ta sẽ nên tạo ra một Exception đúng nhất trong trường hợp của bài thực hành này. Custom Exception này sẽ thuộc loại Checked Exception. Và Custom Exception này còn có thể được điều chỉnh để có thể hiển thị lỗi rõ ràng hơn nữa.

Custom Exception mà chúng ta đang nói đến sẽ có cái tên AgeCheckingException. Bạn hãy tạo mới một lớp như sau.

public class AgeCheckingException extends Exception {

	public AgeCheckingException(String message) {
		super(message);
	}
	
	@Override
	public String getMessage() {
		return "Lỗi nhập vào một tuổi: " + super.getMessage();
	} 
}

Rất đơn giản. Custom Exception này sẽ kế thừa từ lớp Exception. Như vậy đây chính là một Checked Exception rồi. Bạn cũng nên xây dựng một constructor cho nó để nhận vào một message rồi truyền message này cho lớp cha thông qua lời gọi super(). Cuối cùng, Custom Exception này sẽ phủ quyết phương thức getMessage() để trả về nhiều thông tin hơn.

Chúng ta sẽ xây dựng phương thức nhapTuoiNhanVien() như sau. Chắc bạn đã hiểu và quen thuộc với các cách sử dụng Exception như sau rồi nên mình không giải thích thêm.

private static int nhapTuoiNhanVien() throws AgeCheckingException {
	Scanner scanner = new Scanner(System.in);
	System.out.print("Hãy nhập tuổi nhân viên: ");
	int tuoi = 0;
	try {
		tuoi = scanner.nextInt();
		if (tuoi < 0) throw new AgeCheckingException("tuổi không được nhỏ hơn 0.");
	} catch (InputMismatchException e) {
		throw new AgeCheckingException("tuổi phải là một số.");
	}
	return tuoi;
}

Và rồi ở phương thức main() nơi gọi đền nhapTuoiNhanVien() chúng ta sẽ try catch như sau.

public static void main(String[] args) {
	try {
		int tuoi = nhapTuoiNhanVien();
		System.out.println("Tuổi đã nhập: " + tuoi);
	} catch (AgeCheckingException e) {
		System.out.println(e.getMessage());
	}
}

Kết quả sẽ như sau nếu bạn cố tình nhập vào một số âm.

Custom Exception - Báo lỗi tuổi nhỏ hơn 0

Hay khi nhập một số có kèm ký tự.

Custom Exception - Báo lỗi tuổi không phải số

Như vậy kết thúc bài học hôm nay chúng ta cũng đã xong luôn kiến thức thú vị về Exception. Bài sau sẽ là một kiến thức thú vị khác của Java, đó là kiến thức về Thread.

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ẽ bước qua kiến thức cực kỳ thú vị về Thread trong Java.

Advertisements
Rating: 5.0/5. From 7 votes.
Please wait...

5 comments

  1. Chỉ muốn hỏi ad là . chuỗi bài học về Java này bao giờ thì sẽ Hết ( kết thúc ) bài 40 có phải là bài cuối cùng không hay còn bao nhiêu bài nữa ?

    1. Ngoài bài Thread ra mình còn nói về Input/Output, Generic, Collections, Enum, Biểu thức chính quy. Cũng còn chưa biết rằng liệu các bài này có cần chia nhỏ làm các bài nhỏ hơn hay không. Nên mình cũng chưa biết khi nào các bài về Java sẽ hết, nhưng cũng không còn dài nữa đâu, bạn Quỳnh cố gắng theo dõi một thời gian nữa nhé.

  2. Những bài viết của Ad rất hay và dễ hiểu, cảm ơn Ad rất nhiều! Việc học Java trở nên đơn giản hơn khi có yellowcodebooks.com

    1. Cảm ơn bạn Nghĩa, mình xem đây là một động viên lớn lao cho mục đích xây dựng trang blog này của mình 😉

Gửi phản hồi