1. Các loại ngoại lệ

Các loại ngoại lệ

Tất cả các ngoại lệ được chia thành 4 loại, đây thực sự là các lớp kế thừa lẫn nhau.

Throwablelớp học

Lớp cơ sở cho tất cả các ngoại lệ là Throwablelớp. Lớp Throwablechứa mã ghi ngăn xếp cuộc gọi hiện tại (dấu vết ngăn xếp của phương thức hiện tại) vào một mảng. lát nữa chúng ta sẽ tìm hiểu dấu vết ngăn xếp là gì.

Toán tử ném chỉ có thể chấp nhận một đối tượng xuất phát từ Throwablelớp. Và mặc dù về mặt lý thuyết, bạn có thể viết mã như thế throw new Throwable();, nhưng không ai thường làm điều này. Mục đích chính của Throwablelớp là có một lớp cha duy nhất cho tất cả các ngoại lệ.

Errorlớp học

Lớp ngoại lệ tiếp theo là Errorlớp kế thừa trực tiếp Throwablelớp. Máy Java tạo các đối tượng của Errorlớp (và hậu duệ của nó) khi xảy ra sự cố nghiêm trọng . Ví dụ: trục trặc phần cứng, không đủ bộ nhớ, v.v.

Thông thường, với tư cách là một lập trình viên, bạn không thể làm gì trong tình huống xảy ra lỗi như vậy (loại lỗi nên Errorđược đưa ra) trong chương trình: những lỗi này quá nghiêm trọng. Tất cả những gì bạn có thể làm là thông báo cho người dùng rằng chương trình đang gặp sự cố và/hoặc ghi tất cả thông tin đã biết về lỗi vào nhật ký chương trình.

Exceptionlớp học

Các lớp ExceptionRuntimeExceptiondành cho các lỗi phổ biến xảy ra trong hoạt động của nhiều phương thức. Mục tiêu của mỗi ngoại lệ được ném là bị bắt bởi một catchkhối biết cách xử lý nó đúng cách.

Khi một phương thức không thể hoàn thành công việc của nó vì một lý do nào đó, nó sẽ ngay lập tức thông báo cho phương thức đang gọi bằng cách đưa ra một ngoại lệ thuộc loại thích hợp.

Nói cách khác, nếu một biến bằng null, phương thức sẽ đưa ra một NullPointerException. Nếu các đối số không chính xác được truyền cho phương thức, nó sẽ đưa ra một tệp InvalidArgumentException. Nếu phương thức vô tình chia cho 0, nó sẽ ném ra một ArithmeticException.

RuntimeExceptionlớp học

RuntimeExceptionslà tập con của Exceptions. Chúng tôi thậm chí có thể nói rằng đó RuntimeExceptionlà phiên bản nhẹ của các ngoại lệ thông thường ( Exception) — ít yêu cầu và hạn chế hơn đối với các ngoại lệ đó

Bạn sẽ học được sự khác biệt giữa ExceptionRuntimeExceptionsau này.


2. Throws: kiểm tra ngoại lệ

Ném: ngoại lệ được kiểm tra

Tất cả các ngoại lệ Java thuộc 2 loại: được chọnkhông được chọn .

Tất cả các ngoại lệ kế thừa RuntimeExceptionhoặc Errorđược coi là ngoại lệ không được kiểm tra . Tất cả những người khác được kiểm tra ngoại lệ .

Quan trọng!

Hai mươi năm sau khi các ngoại lệ được kiểm tra được giới thiệu, hầu hết mọi lập trình viên Java đều coi đây là một lỗi. Trong các khung hiện đại phổ biến, 95% tất cả các ngoại lệ đều không được chọn. Ngôn ngữ C#, gần như sao chép chính xác Java, không thêm các ngoại lệ được kiểm tra .

Sự khác biệt chính giữa ngoại lệ được kiểm trakhông được kiểm tra là gì ?

Có các yêu cầu bổ sung áp dụng cho các trường hợp ngoại lệ được kiểm tra . Nói một cách đại khái, chúng là:

yêu cầu 1

Nếu một phương thức đưa ra một ngoại lệ được kiểm tra , nó phải chỉ ra loại ngoại lệ trong chữ ký của nó . Theo cách đó, mọi phương thức gọi nó đều biết rằng "ngoại lệ có ý nghĩa" này có thể xảy ra trong đó.

Chỉ ra các ngoại lệ được kiểm tra sau các tham số phương thức sau throwstừ khóa (không sử dụng throwtừ khóa do nhầm lẫn). Nó trông giống như thế này:

type method (parameters) throws exception

Ví dụ:

kiểm tra ngoại lệ ngoại lệ không được kiểm tra
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

Trong ví dụ bên phải, mã của chúng tôi đưa ra một ngoại lệ không được kiểm tra — không cần thực hiện thêm hành động nào. Trong ví dụ bên trái, phương thức đưa ra một ngoại lệ được kiểm tra , vì vậy throwstừ khóa được thêm vào chữ ký phương thức cùng với loại ngoại lệ.

Nếu một phương thức muốn đưa ra nhiều ngoại lệ được kiểm tra , thì tất cả chúng phải được chỉ định sau throwstừ khóa, được phân tách bằng dấu phẩy. Thứ tự không quan trọng. Ví dụ:

public void calculate(int n) throws Exception, IOException
{
   if (n == 0)
      throw new Exception("n is null!");
   if (n == 1)
      throw new IOException("n is 1");
}

yêu cầu 2

Nếu bạn gọi một phương thức đã kiểm tra các ngoại lệ trong chữ ký của nó, thì bạn không thể bỏ qua thực tế là nó ném ra chúng.

Bạn phải nắm bắt tất cả các ngoại lệ như vậy bằng cách thêm catchcác khối cho từng ngoại lệ hoặc bằng cách thêm chúng vào một throwsmệnh đề cho phương thức của bạn.

Như thể chúng ta đang nói, " Những ngoại lệ này quan trọng đến mức chúng ta phải nắm bắt chúng. Và nếu chúng ta không biết cách xử lý chúng, thì bất kỳ ai có thể gọi phương thức của chúng ta đều phải được thông báo rằng những ngoại lệ như vậy có thể xảy ra trong đó.

Ví dụ:

Hãy tưởng tượng rằng chúng ta đang viết một phương thức để tạo ra một thế giới có con người sinh sống. Số lượng người ban đầu được thông qua như một đối số. Vì vậy, chúng ta cần thêm trường hợp ngoại lệ nếu có quá ít người.

tạo trái đất Ghi chú
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
Phương pháp này có khả năng đưa ra hai ngoại lệ được kiểm tra :

  • EmptyWorldException
  • Thế giới cô đơnNgoại lệ

Cuộc gọi phương thức này có thể được xử lý theo 3 cách:

1. Không bắt bất kỳ trường hợp ngoại lệ nào

Điều này thường được thực hiện khi phương pháp không biết cách xử lý tình huống đúng cách.

Mã số Ghi chú
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
Phương thức gọi không bắt các ngoại lệ và phải thông báo cho người khác về chúng: nó thêm chúng vào throwsmệnh đề riêng của nó

2. Nắm bắt một số ngoại lệ

Chúng tôi xử lý các lỗi chúng tôi có thể xử lý. Nhưng những cái chúng tôi không hiểu, chúng tôi ném chúng vào phương thức gọi. Để làm điều này, chúng ta cần thêm tên của chúng vào mệnh đề throws:

Mã số Ghi chú
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
Người gọi chỉ bắt được một ngoại lệ được kiểm traLonelyWorldException. Ngoại lệ khác phải được thêm vào chữ ký của nó, chỉ ra nó sau throwstừ khóa

3. Bắt tất cả các ngoại lệ

Nếu phương thức không đưa ra ngoại lệ cho phương thức gọi, thì phương thức gọi luôn tự tin rằng mọi thứ đều hoạt động tốt. Và nó sẽ không thể thực hiện bất kỳ hành động nào để khắc phục các tình huống ngoại lệ.

Mã số Ghi chú
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
Tất cả các ngoại lệ đều bị bắt trong phương pháp này. Người gọi sẽ tự tin rằng mọi thứ diễn ra tốt đẹp.


3. Gói ngoại lệ

Các trường hợp ngoại lệ được kiểm tra có vẻ hay về mặt lý thuyết, nhưng hóa ra lại là một sự thất vọng lớn trong thực tế.

Giả sử bạn có một phương pháp siêu phổ biến trong dự án của mình. Nó được gọi từ hàng trăm nơi trong chương trình của bạn. Và bạn quyết định thêm một ngoại lệ được kiểm tra mới vào nó. Và có thể ngoại lệ được kiểm tra này thực sự quan trọng và đặc biệt đến mức chỉ có main()phương thức mới biết phải làm gì nếu nó bị bắt.

Điều đó có nghĩa là bạn sẽ phải thêm ngoại lệ được kiểm tra vào throwsmệnh đề của mọi phương thức gọi phương thức siêu phổ biến của bạn . Cũng như trong throwsmệnh đề của tất cả các phương thức gọi các phương thức đó. Và của các phương thức gọi các phương thức đó.

Kết quả là, throwscác mệnh đề của một nửa số phương pháp trong dự án có một ngoại lệ được kiểm tra mới . Và tất nhiên, dự án của bạn được bao phủ bởi các bài kiểm tra và bây giờ các bài kiểm tra không được biên dịch. Và bây giờ bạn cũng phải chỉnh sửa các mệnh đề ném trong các bài kiểm tra của mình.

Và sau đó tất cả mã của bạn (tất cả các thay đổi trong hàng trăm tệp) sẽ phải được xem xét bởi các lập trình viên khác. Và tại thời điểm này, chúng tôi tự hỏi tại sao chúng tôi lại thực hiện quá nhiều thay đổi đẫm máu đối với dự án? (Những) ngày làm việc và các bài kiểm tra bị hỏng - tất cả chỉ vì mục đích thêm một ngoại lệ được kiểm tra ?

Và tất nhiên, vẫn có những vấn đề liên quan đến kế thừa và ghi đè phương thức. Các vấn đề đến từ các ngoại lệ được kiểm tra lớn hơn nhiều so với lợi ích. Điểm mấu chốt là bây giờ ít người yêu thích chúng và ít người sử dụng chúng.

Tuy nhiên, vẫn còn rất nhiều mã (bao gồm cả mã thư viện Java chuẩn) chứa các ngoại lệ được kiểm tra này . Phải làm gì với chúng? Chúng tôi không thể bỏ qua chúng và chúng tôi không biết làm thế nào để xử lý chúng.

Các lập trình viên Java đã đề xuất bọc các ngoại lệ đã kiểm tra trong RuntimeException. Nói cách khác, hãy nắm bắt tất cả các ngoại lệ được kiểm tra , sau đó tạo các ngoại lệ không được kiểm tra (ví dụ: RuntimeException) và ném chúng thay vào đó. Làm điều đó trông giống như thế này:

try
{
   // Code where a checked exception might occur
}
catch(Exception exp)
{
   throw new RuntimeException(exp);
}

Đó không phải là một giải pháp hay lắm, nhưng không có gì tội phạm ở đây: ngoại lệ chỉ đơn giản là được nhét vào bên trong tệp RuntimeException.

Nếu muốn, bạn có thể dễ dàng lấy nó từ đó. Ví dụ:

Mã số Ghi chú
try
{
   // Code where we wrap the checked exception
   // in a RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // Exception handling code goes here
   }
}







Nhận ngoại lệ được lưu trữ bên trong RuntimeExceptionđối tượng. Biến causecó thể null

Xác định loại của nó và chuyển nó thành loại ngoại lệ được kiểm tra .


4. Bắt nhiều ngoại lệ

Các lập trình viên thực sự ghét phải sao chép mã. Họ thậm chí còn đưa ra một nguyên tắc phát triển tương ứng: Đừng lặp lại chính mình (DRY) . Nhưng khi xử lý các ngoại lệ, thường xảy ra trường hợp một trykhối được theo sau bởi một số catchkhối có cùng mã.

Hoặc có thể có 3 catchkhối có cùng mã và 2 catchkhối khác có mã giống hệt nhau. Đây là một tình huống tiêu chuẩn khi dự án của bạn xử lý các ngoại lệ một cách có trách nhiệm.

Bắt đầu với phiên bản 7, trong ngôn ngữ Java đã thêm khả năng chỉ định nhiều loại ngoại lệ trong một catchkhối. Nó trông giống như thế này:

try
{
   // Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
   // Exception handling code
}

Bạn có thể có bao nhiêu catchkhối tùy thích. Tuy nhiên, một catchkhối duy nhất không thể chỉ định các ngoại lệ kế thừa lẫn nhau. Nói cách khác, bạn không thể viết catch ( Exception| RuntimeExceptione ), bởi vì RuntimeExceptionlớp kế thừa Exception.



5. Ngoại lệ tùy chỉnh

Bạn luôn có thể tạo lớp ngoại lệ của riêng mình. Bạn chỉ cần tạo một lớp kế thừa RuntimeExceptionlớp đó. Nó sẽ trông giống như thế này:

class ClassName extends RuntimeException
{
}

Chúng ta sẽ thảo luận chi tiết khi bạn tìm hiểu về OOP, tính kế thừa, hàm tạo và ghi đè phương thức.

Tuy nhiên, ngay cả khi bạn chỉ có một lớp đơn giản như thế này (hoàn toàn không có mã), bạn vẫn có thể đưa ra các ngoại lệ dựa trên nó:

Mã số Ghi chú
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




Ném một không được kiểm soát MyException .

Trong nhiệm vụ Đa luồng Java , chúng ta sẽ đi sâu vào làm việc với các ngoại lệ tùy chỉnh của riêng mình.