Cách xử lý các ngoại lệ trong Java một cách đúng đắn

Cách xử lý các ngoại lệ trong Java một cách đúng đắn

Là một người mới lập trình, khái niệm về xử lý ngoại lệ có thể khó khăn để quấn quanh đầu của bạn. Không phải bản thân khái niệm này là khó, nhưng thuật ngữ có thể làm cho nó có vẻ tiên tiến hơn nó. Và đó là một tính năng mạnh mẽ đến mức dễ bị lạm dụng và lạm dụng.





Trong bài viết này, bạn sẽ tìm hiểu các ngoại lệ là gì, tại sao chúng lại quan trọng, cách sử dụng chúng và những sai lầm phổ biến cần tránh. Hầu hết các ngôn ngữ hiện đại đều có một số loại xử lý ngoại lệ, vì vậy nếu bạn từng chuyển từ Java, bạn có thể mang theo hầu hết các mẹo này.





Hiểu các ngoại lệ của Java

Trong Java, một ngoại lệ là một đối tượng chỉ ra điều gì đó bất thường (hoặc 'đặc biệt') đã xảy ra trong quá trình chạy ứng dụng của bạn. Những trường hợp ngoại lệ như vậy là ném , về cơ bản có nghĩa là một đối tượng ngoại lệ được tạo (tương tự như cách các lỗi được 'nâng cao').





Vẻ đẹp là bạn có thể chụp lấy đã đưa ra các ngoại lệ, cho phép bạn đối phó với tình trạng bất thường và cho phép ứng dụng của bạn tiếp tục chạy như thể không có gì xảy ra. Ví dụ: trong khi con trỏ null trong C có thể làm hỏng ứng dụng của bạn, Java cho phép bạn ném và bắt

NullPointerException

s trước khi biến null có cơ hội gây ra sự cố.



Hãy nhớ rằng, một ngoại lệ chỉ là một đối tượng, nhưng có một đặc điểm quan trọng: nó phải được mở rộng từ

Exception

lớp hoặc bất kỳ lớp con nào của





Exception

. Mặc dù Java có tất cả các loại ngoại lệ tích hợp sẵn, bạn cũng có thể tạo riêng nếu muốn. Một số các ngoại lệ Java phổ biến nhất bao gồm:

  • NullPointerException
  • NumberFormatException
  • IllegalArgumentException
  • RuntimeException
  • IllegalStateException

Vì vậy, điều gì sẽ xảy ra khi bạn ném một ngoại lệ?





Đầu tiên, Java xem xét phương thức tức thời để xem liệu có mã nào xử lý loại ngoại lệ bạn đã ném hay không. Nếu một trình xử lý không tồn tại, nó sẽ xem xét phương thức được gọi là phương thức hiện tại để xem liệu một xử lý có tồn tại ở đó hay không. Nếu không, nó sẽ xem xét phương thức đã gọi điều đó và sau đó là phương pháp tiếp theo, v.v. Nếu không bắt được ngoại lệ, ứng dụng sẽ in dấu vết ngăn xếp và sau đó bị treo. (Trên thực tế, nó có nhiều sắc thái hơn là chỉ đơn giản là gặp sự cố, nhưng đó là một chủ đề nâng cao ngoài phạm vi của bài viết này.)

ĐẾN dấu vết ngăn xếp là danh sách tất cả các phương thức mà Java đã duyệt qua trong khi tìm kiếm một trình xử lý ngoại lệ. Đây là một dấu vết ngăn xếp trông như thế nào:

Exception in thread 'main' java.lang.NullPointerException
at com.example.myproject.Book.getTitle(Book.java:16)
at com.example.myproject.Author.getBookTitles(Author.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Chúng ta có thể thu lượm được rất nhiều điều từ điều này. Đầu tiên, ngoại lệ được ném là một

NullPointerException

. Nó xảy ra trong

getTitle()

trên dòng 16 của Book.java. Phương thức đó được gọi từ

getBookTitles()

trên dòng 25 của Author.java. Điều đó phương thức được gọi từ

main()

trên dòng 14 của Bootstrap.java. Như bạn có thể thấy, biết tất cả những điều này làm cho việc gỡ lỗi trở nên dễ dàng hơn.

Nhưng một lần nữa, lợi ích thực sự của các ngoại lệ là bạn có thể 'xử lý' tình trạng bất thường bằng cách bắt ngoại lệ, thiết lập mọi thứ đúng và tiếp tục ứng dụng mà không gặp sự cố.

Sử dụng ngoại lệ Java trong mã

Giả sử bạn có

someMethod()

nhận một số nguyên và thực thi một số logic có thể bị phá vỡ nếu số nguyên nhỏ hơn 0 hoặc lớn hơn 100. Đây có thể là một nơi tốt để đưa ra một ngoại lệ:

tôi có thể nâng cấp bộ nhớ macbook pro của mình không
public void someMethod(int value) {
if (value 100) {
throw new
IllegalArgumentException

Để nắm bắt được trường hợp ngoại lệ này, bạn cần phải đến nơi

someMethod()

được gọi và sử dụng khối thử bắt :

public void callingMethod() {
try {
someMethod(200);
someOtherMethod();
} catch (IllegalArgumentException e) {
// handle the exception in here
}
// ...
}

Mọi thứ trong cố gắng khối sẽ thực thi theo thứ tự cho đến khi một ngoại lệ được ném ra. Ngay sau khi một ngoại lệ được ném ra, tất cả các câu lệnh tiếp theo sẽ bị bỏ qua và logic ứng dụng ngay lập tức chuyển đến chụp lấy khối.

Trong ví dụ của chúng tôi, chúng tôi nhập khối thử và ngay lập tức gọi

someMethod()

. Vì 200 không nằm trong khoảng từ 0 đến 100, một

IllegalArgumentException

được ném. Điều này ngay lập tức kết thúc việc thực thi

someMethod()

, bỏ qua phần còn lại của logic trong khối try (

someOtherMethod()

không bao giờ được gọi) và tiếp tục thực thi trong khối bắt.

Điều gì sẽ xảy ra nếu chúng tôi gọi

someMethod(50)

thay thế? Các

IllegalArgumentException

sẽ không bao giờ bị ném.

someMethod()

sẽ thực thi như bình thường. Khối try sẽ thực thi như bình thường, gọi

someOtherMethod()

khi someMethod () hoàn thành. Khi nào

someOtherMethod()

kết thúc, khối bắt sẽ bị bỏ qua và

callingMethod()

sẽ tiếp tục.

Lưu ý rằng bạn có thể có nhiều khối bắt mỗi khối thử:

public void callingMethod() {
try {
someMethod(200);
someOtherMethod();
} catch (IllegalArgumentException e) {
// handle the exception in here
} catch (NullPointerException e) {
// handle the exception in here
}
// ...
}

Cũng lưu ý rằng một tùy chọn cuối cùng khối cũng tồn tại:

public void method() {
try {
// ...
} catch (Exception e) {
// ...
} finally {
// ...
}
}

Mã trong một khối cuối cùng là luôn thực hiện không có vấn đề gì. Nếu bạn có một câu lệnh return trong khối try, khối cuối cùng sẽ được thực thi trước khi trả về phương thức. Nếu bạn ném một ngoại lệ khác vào khối catch, khối cuối cùng sẽ được thực thi trước khi ngoại lệ được ném ra.

Bạn nên sử dụng khối cuối cùng khi bạn có các đối tượng cần được dọn dẹp trước khi phương thức kết thúc. Ví dụ: nếu bạn đã mở một tệp trong khối try và sau đó ném một ngoại lệ, khối cuối cùng sẽ cho phép bạn đóng tệp trước khi rời khỏi phương thức.

Lưu ý rằng bạn có thể có khối cuối cùng mà không có khối bắt:

public void method() {
try {
// ...
} finally {
// ...
}
}

Điều này cho phép bạn thực hiện bất kỳ thao tác dọn dẹp cần thiết nào trong khi vẫn cho phép các ngoại lệ được ném ra để phổ biến ngăn xếp gọi phương thức (tức là bạn không muốn xử lý ngoại lệ ở đây nhưng trước tiên bạn vẫn cần phải dọn dẹp).

Đã kiểm tra và ngoại lệ không kiểm tra trong Java

Không giống như hầu hết các ngôn ngữ, Java phân biệt giữa đã kiểm tra ngoại lệngoại lệ không được kiểm tra (ví dụ: C # chỉ có các ngoại lệ không được chọn). Một ngoại lệ đã được kiểm tra cần phải bị mắc kẹt trong phương thức mà ngoại lệ được ném ra, nếu không mã sẽ không biên dịch.

Để tạo một ngoại lệ đã chọn, hãy mở rộng từ

Exception

. Để tạo một ngoại lệ không được chọn, hãy mở rộng từ

RuntimeException

.

Bất kỳ phương thức nào ném một ngoại lệ đã kiểm tra phải biểu thị điều này trong chữ ký phương thức bằng cách sử dụng ném từ khóa. Kể từ khi Java được tích hợp sẵn

IOException

là một ngoại lệ đã được kiểm tra, mã sau sẽ không biên dịch:

public void wontCompile() {
// ...
if (someCondition) {
throw new IOException();
}
// ...
}

Trước tiên, bạn phải khai báo rằng nó ném một ngoại lệ đã kiểm tra:

public void willCompile() throws IOException {
// ...
if (someCondition) {
throw new IOException();
}
// ...
}

Lưu ý rằng một phương thức có thể được khai báo là ném một ngoại lệ nhưng không bao giờ thực sự ném một ngoại lệ. Mặc dù vậy, ngoại lệ sẽ vẫn cần được bắt hoặc nếu không mã sẽ không biên dịch.

Khi nào bạn nên sử dụng các ngoại lệ được chọn hoặc không được chọn?

Tài liệu Java chính thức có trang cho câu hỏi này . Nó tổng kết sự khác biệt bằng một quy tắc ngón tay cái ngắn gọn: 'Nếu một khách hàng có thể được mong đợi một cách hợp lý để phục hồi sau một ngoại lệ, hãy đặt nó thành một ngoại lệ đã được kiểm tra. Nếu khách hàng không thể làm bất cứ điều gì để khôi phục ngoại lệ, hãy đặt nó thành ngoại lệ không được chọn. '

Nhưng hướng dẫn này có thể đã lỗi thời. Mặt khác, các ngoại lệ được kiểm tra sẽ dẫn đến mã mạnh hơn. Mặt khác, không có ngôn ngữ nào khác kiểm tra các ngoại lệ theo cách tương tự như Java, điều này cho thấy hai điều: một, tính năng này không đủ hữu ích để các ngôn ngữ khác đánh cắp nó, và hai, bạn hoàn toàn có thể sống mà không có chúng. Ngoài ra, các ngoại lệ đã kiểm tra không hoạt động tốt với các biểu thức lambda được giới thiệu trong Java 8.

Hướng dẫn sử dụng ngoại lệ trong Java

Các ngoại lệ rất hữu ích nhưng dễ bị lạm dụng và lạm dụng. Dưới đây là một số mẹo và phương pháp hay nhất để giúp bạn tránh làm lộn xộn chúng.

  • Ưu tiên các trường hợp ngoại lệ cụ thể so với các trường hợp ngoại lệ chung. Sử dụng NumberFormatException hết IllegalArgumentException khi có thể, nếu không thì sử dụng IllegalArgumentException hết RuntimeException khi có thể.
  • Không bao giờ bắt Throwable ! Exception lớp thực sự mở rộng Throwable và khối catch thực sự hoạt động với Throwable hoặc bất kỳ lớp nào mở rộng Throwable. Tuy nhiên, dấu Error lớp cũng mở rộng Throwable và bạn không bao giờ muốn bắt một ErrorError s chỉ ra các vấn đề nghiêm trọng không thể phục hồi.
  • Không bao giờ bắt Exception ! InterruptedException mở rộng Exception , vì vậy bất kỳ khối nào bắt được Exception cũng sẽ bắt InterruptedException , và đó là một ngoại lệ rất quan trọng mà bạn không muốn gặp phải (đặc biệt là trong các ứng dụng đa luồng) trừ khi bạn biết mình đang làm gì. Nếu bạn không biết phải bắt ngoại lệ nào, hãy xem xét không bắt bất cứ thứ gì.
  • Sử dụng thông báo mô tả để dễ gỡ lỗi. Khi bạn ném một ngoại lệ, bạn có thể cung cấp một dấu String tin nhắn như một đối số. Thông báo này có thể được truy cập trong khối bắt bằng cách sử dụng Exception.getMessage() , nhưng nếu ngoại lệ không bao giờ bị bắt, thông báo cũng sẽ xuất hiện như một phần của dấu vết ngăn xếp.
  • Cố gắng không nắm bắt và bỏ qua các trường hợp ngoại lệ. Để tránh sự bất tiện của các ngoại lệ đã được kiểm tra, rất nhiều lập trình viên mới và lười biếng sẽ thiết lập một khối bắt nhưng để trống. Tồi tệ! Luôn xử lý nó một cách duyên dáng, nhưng nếu bạn không thể, thì ít nhất hãy in ra một dấu vết ngăn xếp để bạn biết rằng ngoại lệ đã được ném ra. Bạn có thể làm điều này bằng cách sử dụng Exception.printStackTrace() phương pháp.
  • Cẩn thận với việc lạm dụng các ngoại lệ. Khi bạn có một cái búa, mọi thứ trông giống như một cái đinh. Khi bạn lần đầu tiên tìm hiểu về các ngoại lệ, bạn có thể cảm thấy có nghĩa vụ phải biến mọi thứ thành ngoại lệ ... đến mức phần lớn quy trình kiểm soát của ứng dụng của bạn chuyển sang xử lý ngoại lệ. Hãy nhớ rằng, ngoại lệ có nghĩa là cho các trường hợp 'đặc biệt'!

Bây giờ bạn sẽ đủ thoải mái với các ngoại lệ để hiểu chúng là gì, tại sao chúng được sử dụng và cách kết hợp chúng vào mã của riêng bạn. Nếu bạn không hiểu đầy đủ về khái niệm, không sao cả! Tôi mất một lúc để nó 'nhấp' vào đầu mình, vì vậy đừng cảm thấy như bạn cần phải vội vàng. Hãy dành thời gian của bạn.

Có bất kỳ câu hỏi? Biết về bất kỳ mẹo nào khác liên quan đến ngoại lệ mà tôi đã bỏ qua không? Chia sẻ chúng trong các bình luận bên dưới!

Đăng lại Đăng lại tiếng riu ríu E-mail Cách tạo sơ đồ luồng dữ liệu để trực quan hóa dữ liệu của bất kỳ dự án nào

Biểu đồ luồng dữ liệu (DFD) của bất kỳ quy trình nào giúp bạn hiểu cách dữ liệu di chuyển từ nguồn đến đích. Đây là cách tạo nó!

Đọc tiếp
Chủ đề liên quan
  • Lập trình
  • Java
Giới thiệu về tác giả Joel lee(1524 bài báo đã được xuất bản)

Joel Lee là Tổng biên tập của MakeUseOf từ năm 2018. Anh ấy có bằng B.S. về Khoa học Máy tính và hơn chín năm kinh nghiệm viết và chỉnh sửa chuyên nghiệp.

giám sát hoạt động mac kernel_task
Xem thêm từ Joel Lee

Theo dõi bản tin của chúng tôi

Tham gia bản tin của chúng tôi để biết các mẹo công nghệ, đánh giá, sách điện tử miễn phí và các ưu đãi độc quyền!

Bấm vào đây để đăng ký