CodeGym /Blog Java /Ngẫu nhiên /Ngoại lệ: bắt và xử lý

Ngoại lệ: bắt và xử lý

Xuất bản trong nhóm
CHÀO! Tôi ghét phải đề cập đến nó, nhưng một phần lớn công việc của lập trình viên là xử lý lỗi. Thông thường nhất, của riêng mình. Hóa ra không có người không mắc sai lầm. Và cũng không có chương trình nào như vậy. Ngoại lệ: bắt và xử lý - 1 Tất nhiên, khi xử lý một lỗi, điều chính là phải hiểu nguyên nhân của nó. Và rất nhiều thứ có thể gây ra lỗi trong một chương trình. Tại một số thời điểm, những người tạo ra Java đã tự hỏi mình nên làm gì với các lỗi lập trình có khả năng xảy ra nhất? Hoàn toàn tránh chúng là không thực tế, các lập trình viên có khả năng viết những thứ mà bạn thậm chí không thể tưởng tượng được. :) Vì vậy, chúng ta cần cung cấp cho ngôn ngữ một cơ chế để xử lý lỗi. Nói cách khác, nếu có lỗi trong chương trình của bạn, bạn cần một số loại kịch bản để biết phải làm gì tiếp theo. Chính xác thì chương trình nên làm gì khi xảy ra lỗi? Hôm nay chúng ta sẽ làm quen với cơ chế này. Nó được gọi là " ngoại lệ trong Java ".

Ngoại lệ là gì?

Một ngoại lệ là một tình huống đặc biệt, ngoài dự kiến ​​xảy ra trong khi một chương trình đang chạy. Có rất nhiều trường hợp ngoại lệ. Ví dụ: bạn đã viết mã để đọc văn bản từ một tệp và hiển thị dòng đầu tiên.

public class Main {

   public static void main(String[] args) throws IOException {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   }
}
Nhưng nếu không có tệp đó thì sao! Chương trình sẽ tạo ra một ngoại lệ: FileNotFoundException. Đầu ra: Ngoại lệ trong luồng "chính" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Hệ thống không thể tìm thấy đường dẫn đã chỉ định) Trong Java, mỗi ngoại lệ được đại diện bởi một lớp riêng biệt. Tất cả các lớp ngoại lệ này bắt nguồn từ một "tổ tiên" chung - Throwablelớp cha. Tên của một lớp ngoại lệ thường phản ánh chính xác lý do tại sao ngoại lệ xảy ra:
  • FileNotFoundException(không tìm thấy tệp)

  • ArithmeticException(một ngoại lệ xảy ra trong khi thực hiện một phép toán)

  • ArrayIndexOutOfBoundsException(chỉ mục nằm ngoài giới hạn của mảng). Ví dụ: ngoại lệ này xảy ra nếu bạn cố gắng hiển thị vị trí 23 của một mảng chỉ có 10 phần tử.
Tổng cộng, Java có gần 400 lớp như vậy! Tại sao rất nhiều? Để làm cho chúng thuận tiện hơn cho các lập trình viên làm việc. Hãy tưởng tượng điều này: bạn viết một chương trình, và trong khi nó chạy, nó tạo ra một ngoại lệ giống như sau:

Exception in thread "main"
Uhhhh. :/ Điều đó không giúp được gì nhiều. Không rõ lỗi có nghĩa là gì hoặc nó đến từ đâu. Không có thông tin hữu ích ở đây. Nhưng số lượng lớn các lớp ngoại lệ trong Java cung cấp cho lập trình viên điều quan trọng nhất: loại lỗi và nguyên nhân có thể xảy ra của nó (được nhúng trong tên lớp). Đó là một điều hoàn toàn khác để xem

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)
Ngay lập tức rõ ràng vấn đề có thể là gì và bắt đầu tìm hiểu từ đâu để giải quyết vấn đề! Các ngoại lệ, giống như các thể hiện của bất kỳ lớp nào, là các đối tượng.

Bắt và xử lý ngoại lệ

Java có các khối mã đặc biệt để làm việc với các ngoại lệ: try, catchfinally. Ngoại lệ: bắt và xử lý - 2 Mã nơi lập trình viên tin rằng một ngoại lệ có thể xảy ra được đặt trong trykhối. Điều đó không có nghĩa là một ngoại lệ sẽ xảy ra ở đây. Điều đó có nghĩa là nó có thể xảy ra ở đây và lập trình viên nhận thức được khả năng này. Loại lỗi bạn muốn xảy ra được đặt trong catchkhối. Điều này cũng chứa tất cả mã sẽ được thực thi nếu xảy ra ngoại lệ. Đây là một ví dụ:

public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println("Error! File not found!");
   }
}
Đầu ra: Lỗi! Không tìm thấy tệp! Chúng tôi đặt mã của chúng tôi trong hai khối. Trong khối đầu tiên, chúng tôi dự đoán rằng lỗi "Không tìm thấy tệp" có thể xảy ra. Đây là trykhối. Trong phần thứ hai, chúng tôi cho chương trình biết phải làm gì nếu xảy ra lỗi. Và loại lỗi cụ thể: FileNotFoundException. Nếu chúng ta đặt một lớp ngoại lệ khác trong dấu ngoặc đơn của catchkhối, thì FileNotFoundExceptionsẽ không bị phát hiện.

public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (ArithmeticException e) {

       System.out.println("Error! File not found!");
   }
}
Đầu ra: Ngoại lệ trong luồng "chính" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Hệ thống không thể tìm thấy đường dẫn đã chỉ định) Mã trong catchkhối không chạy, vì chúng tôi đã "đặt cấu hình" khối này để bắt ArithmeticExceptionvà mã trong trykhối ném ra một loại khác: FileNotFoundException. Chúng tôi đã không viết bất kỳ mã nào để xử lý FileNotFoundException, vì vậy chương trình sẽ hiển thị thông tin mặc định cho FileNotFoundException. Ở đây bạn cần chú ý đến ba điều. Số một. Khi một ngoại lệ xảy ra trên một số dòng trong trykhối, mã theo sau sẽ không được thực thi. Việc thực thi chương trình ngay lập tức "nhảy" vào catchkhối. Ví dụ:

public static void main(String[] args) {
   try {
       System.out.println("Divide by zero");
       System.out.println(366/0);// This line of code will throw an exception

       System.out.println("This");
       System.out.println("code");
       System.out.println("will not");
       System.out.println("be");
       System.out.println("executed!");

   } catch (ArithmeticException e) {

       System.out.println("The program jumped to the catch block!");
       System.out.println("Error! You can't divide by zero!");
   }
}
Đầu ra: Chia cho 0 Chương trình nhảy đến khối bắt! Lỗi! Bạn không thể chia cho số không! Trên dòng thứ hai của trykhối, chúng tôi cố gắng chia cho 0, kết quả là ArithmeticException. Do đó, dòng 3-9 của trykhối sẽ không được thực thi. Như chúng tôi đã nói, chương trình ngay lập tức bắt đầu thực thi catchkhối. Số hai. Có thể có một số catchkhối. Nếu mã trong trykhối có thể đưa ra không phải một mà là một số loại ngoại lệ khác nhau, bạn có thể viết một catchkhối cho từng loại ngoại lệ đó.

public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       System.out.println(366/0);
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
      
       System.out.println("Error! File not found!");
      
   } catch (ArithmeticException e) {

       System.out.println("Error! Division by 0!");
      
   }
}
Trong ví dụ này, chúng tôi đã viết hai catchkhối. Nếu FileNotFoundExceptionxảy ra trong trykhối, thì catchkhối đầu tiên sẽ được thực thi. Nếu ArithmeticExceptionxảy ra, khối thứ hai sẽ được thực thi. Bạn có thể viết 50 catchkhối nếu muốn. Tất nhiên, tốt hơn là không viết mã có thể đưa ra 50 loại ngoại lệ khác nhau. :) Thứ ba. Làm thế nào để bạn biết những ngoại lệ mà mã của bạn có thể đưa ra? Chà, bạn có thể đoán được một số trong số chúng, nhưng bạn không thể giữ mọi thứ trong đầu. Do đó, trình biên dịch Java biết các ngoại lệ phổ biến nhất và các tình huống mà chúng có thể xảy ra. Ví dụ: nếu bạn viết mã mà trình biên dịch biết có thể đưa ra hai loại ngoại lệ, thì mã của bạn sẽ không biên dịch cho đến khi bạn xử lý chúng. Chúng ta sẽ xem các ví dụ về điều này dưới đây. Bây giờ một số từ về xử lý ngoại lệ. Có 2 cách để xử lý ngoại lệ. Chúng ta đã gặp trường hợp đầu tiên: phương thức có thể tự xử lý ngoại lệ trong một catch()khối. Có một tùy chọn thứ hai: phương thức có thể ném lại ngoại lệ lên ngăn xếp cuộc gọi. Điều đó nghĩa là gì? Ví dụ: chúng ta có một lớp có cùng printFirstString()phương thức đọc một tệp và hiển thị dòng đầu tiên của nó:

public static void printFirstString(String filePath) {

   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
Hiện tại, mã của chúng tôi không biên dịch vì mã có các ngoại lệ chưa được xử lý. Trong dòng 1, bạn chỉ định đường dẫn đến tệp. Trình biên dịch biết rằng mã như vậy có thể dễ dàng tạo tệp FileNotFoundException. Trong dòng 3, bạn đọc văn bản từ tệp. Quá trình này có thể dễ dàng dẫn đến IOException(lỗi đầu vào/đầu ra). Bây giờ trình biên dịch nói với bạn, "Anh bạn, tôi sẽ không phê duyệt mã này và tôi sẽ không biên dịch nó cho đến khi bạn cho tôi biết tôi nên làm gì nếu một trong những ngoại lệ này xảy ra. Và chúng chắc chắn có thể xảy ra dựa trên mã bạn đã viết !" Không có cách nào để vượt qua nó: bạn cần xử lý cả hai! Chúng ta đã biết về phương pháp xử lý ngoại lệ đầu tiên: chúng ta cần đặt mã của mình vào một trykhối và thêm hai catchkhối:

public static void printFirstString(String filePath) {

   try {
       BufferedReader reader = new BufferedReader(new FileReader(filePath));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println("Error, file not found!");
       e.printStackTrace();
   } catch (IOException e) {
       System.out.println("File input/output error!");
       e.printStackTrace();
   }
}
Nhưng đây không phải là lựa chọn duy nhất. Chúng ta có thể ném ngoại lệ lên cao hơn thay vì viết mã xử lý lỗi bên trong phương thức. Điều này được thực hiện bằng cách sử dụng từ khóa throwstrong khai báo phương thức:

public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
Sau từ khóa throws, chúng tôi chỉ ra một danh sách được phân tách bằng dấu phẩy gồm tất cả các loại ngoại lệ mà phương thức có thể đưa ra. Tại sao? Bây giờ, nếu ai đó muốn gọi printFirstString()phương thức trong chương trình, người đó (không phải bạn) sẽ phải thực hiện xử lý ngoại lệ. Ví dụ: giả sử rằng ở đâu đó trong chương trình, một trong những đồng nghiệp của bạn đã viết một phương thức gọi printFirstString()phương thức của bạn:

public static void yourColleagueMethod() {

   // Your colleague's method does something

   //...and then calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
Chúng tôi nhận được một lỗi! Mã này sẽ không được biên dịch! Chúng tôi đã không viết mã xử lý ngoại lệ trong printFirstString()phương thức này. Kết quả là, nhiệm vụ này bây giờ rơi vào vai của những người sử dụng phương pháp này. Nói cách khác, methodWrittenByYourColleague()phương thức hiện có 2 tùy chọn giống nhau: nó phải sử dụng một try-catchkhối để xử lý cả hai ngoại lệ hoặc ném lại chúng.

public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   // The method does something

   //...and then calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
Trong trường hợp thứ hai, phương thức tiếp theo trong ngăn xếp cuộc gọi—phương thức đang gọi methodWrittenByYourColleague()—sẽ phải xử lý các ngoại lệ. Đó là lý do tại sao chúng tôi gọi đây là "ném hoặc chuyển ngoại lệ lên". Nếu bạn ném ngoại lệ lên trên bằng cách sử dụng từ khóa throws, mã của bạn sẽ được biên dịch. Tại thời điểm này, trình biên dịch dường như đang nói, "Được rồi, được rồi. Mã của bạn chứa một loạt các ngoại lệ tiềm ẩn, nhưng tôi sẽ biên dịch nó. Nhưng chúng ta sẽ quay lại cuộc trò chuyện này!" Và khi bạn gọi bất kỳ phương thức nào có ngoại lệ chưa được xử lý, trình biên dịch sẽ thực hiện lời hứa của nó và nhắc bạn về chúng một lần nữa. Cuối cùng, chúng ta sẽ nói về finallykhối (xin lỗi vì chơi chữ). Đây là phần cuối cùng của try-catch-finallybộ ba xử lý ngoại lệ..

public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println("Error! File not found!");
       e.printStackTrace();
   } finally {
       System.out.println ("And here's the finally block!");
   }
}
Trong ví dụ này, mã bên trong finallykhối sẽ được thực thi trong cả hai trường hợp. Nếu mã trong trykhối được chạy đầy đủ mà không đưa ra bất kỳ ngoại lệ nào, thì finallycuối cùng khối sẽ chạy. Nếu mã bên trong trykhối bị gián đoạn bởi một ngoại lệ và chương trình nhảy tới catchkhối, thì finallykhối đó vẫn sẽ chạy theo mã bên trong catchkhối. Tại sao điều này là cần thiết? Mục đích chính của nó là thực thi mã bắt buộc: mã phải được thực hiện bất kể hoàn cảnh nào. Ví dụ, nó thường giải phóng một số tài nguyên được sử dụng bởi chương trình. Trong mã của chúng tôi, chúng tôi mở một luồng để đọc thông tin từ tệp và chuyển nó tới BufferedReaderđối tượng. Chúng tôi phải đóng trình đọc của mình và giải phóng các tài nguyên. Điều này phải được thực hiện bất kể điều gì—khi chương trình hoạt động bình thường và khi nó đưa ra một ngoại lệ. Khối finallylà một nơi rất thuận tiện để làm điều này:

public static void main(String[] args) throws IOException {

   BufferedReader reader = null;
   try {
       reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       e.printStackTrace();
   } finally {
       System.out.println ("And here's the finally block!");
       if (reader != null) {
           reader.close();
       }
   }
}
Bây giờ chúng tôi chắc chắn rằng chúng tôi sẽ chăm sóc các tài nguyên, bất kể điều gì xảy ra khi chương trình đang chạy. :) Đó không phải là tất cả những gì bạn cần biết về ngoại lệ. Xử lý lỗi là một chủ đề siêu quan trọng trong lập trình. Rất nhiều bài báo được dành cho nó. Trong bài học tiếp theo, chúng ta sẽ tìm hiểu xem có những loại ngoại lệ nào và cách tạo ngoại lệ của riêng bạn. :) Gặp bạn sau!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION