Menu

NullPointerException trong Java: nguyên nhân và cách sửa

NullPointerException trong Java thực sự có nghĩa là gì, những cách thường gặp khiến bạn gây ra nó, cách đọc thông báo lỗi và các cách viết code để phòng tránh.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Lỗi Java phổ biến nhất

NullPointerException (ai cũng gọi tắt là NPE) xảy ra khi bạn cố dùng một tham chiếu không trỏ tới gì cả, tức null, như thể nó trỏ tới một đối tượng thật. Biến kiểu đối tượng trong Java hoặc chứa một đối tượng, hoặc chứa null, giá trị mang nghĩa "ở đây không có đối tượng nào". Ngay khi bạn bắt null làm gì đó, như gọi phương thức trên nó, đọc một trường của nó hay truy cập theo chỉ số, thì không có gì ở đó để thao tác, nên JVM ném ra ngoại lệ.

Khác với try-catch, thứ bạn dùng ở trang trước để xử lý các lỗi được dự tính, NPE gần như luôn là một bug đơn thuần. Mục tiêu của trang này không phải là bắt chúng, mà là hiểu vì sao chúng xảy ra và viết code không sinh ra chúng.

Không có String nào để length() chạy trên đó, nên chương trình dừng lại với một NullPointerException.

Đọc thông báo lỗi

Từ Java 14, thông báo lỗi cho bạn biết chính xác thứ gì là null, gọi là Helpful NullPointerException. Hãy đọc nó trước khi sửa bất cứ thứ gì:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
	at Main.main(Main.java:4)

Có hai phần quan trọng. Cannot invoke "String.length()" là thao tác bị lỗi, còn because "name" is null chỉ ra thủ phạm. Dòng at Main.main(Main.java:4) là stack trace trỏ tới đúng dòng code. Vậy cách sửa không phải là "bọc dòng 4 trong try", mà là "tìm hiểu vì sao name là null ở dòng 4". Hầu như lúc nào bug cũng nằm ở đâu đó phía trước, nơi lẽ ra giá trị phải được gán mà lại không.

Những cách gây ra NPE

NPE đến từ một vài thao tác, tất cả đều là biến thể của việc "đụng vào null":

Tra cứu map là nguồn gây lỗi kinh điển: get trả về null khi không có khóa, và NPE thường chỉ lộ ra vài dòng sau đó, khi bạn thực sự dùng giá trị ấy. Unboxing là trường hợp khó thấy: gán một Integer có giá trị null cho một int sẽ ném lỗi, vì không có con số nào để sao chép.

Bảo vệ bằng phép kiểm tra null

Cách phòng thủ đơn giản nhất là một câu lệnh if xác nhận tham chiếu khác null trước khi dùng:

Khi so sánh một biến với một hằng String, hãy đặt hằng lên trước: "yes".equals(answer) thay vì answer.equals("yes"). Nếu answer là null, cách viết đầu tiên bình thản trả về false, còn cách thứ hai sẽ ném lỗi.

Báo lỗi sớm với Objects.requireNonNull

Rải phép kiểm tra null khắp nơi sẽ làm code rối. Khi một giá trị không bao giờ được phép là null, như đối số của một hàm khởi tạo, hãy kiểm tra nó ngay tại điểm đầu vào bằng Objects.requireNonNull. Phương thức này ném lỗi ngay lập tức, kèm thông báo rõ ràng, tại chỗ giá trị sai đi vào, thay vì để lỗi xảy ra sâu bên trong code về sau:

Thói quen "báo lỗi sớm" (fail fast) này biến một NPE mơ hồ cách đó 200 dòng thành một lời báo lỗi chính xác ngay tại nguồn. Việc bắt ngoại lệ ở đây chỉ để hiển thị thông báo; trong code thật, bạn nên để nó lộ ra để bug được sửa.

Tránh null ngay từ đầu

NPE tốt nhất là NPE không bao giờ xảy ra, vì không có null nào để đụng phải. Vài thói quen sau giúp ích rất nhiều:

  • Trả về collection hoặc chuỗi rỗng, đừng bao giờ trả về null. Bạn có thể yên tâm lặp qua hay gọi phương thức trên Collections.emptyList() và "".
  • Dùng getOrDefault với map để một lần tra cứu hụt vẫn cho ra giá trị thật thay vì null.
  • Khởi tạo các trường ngay khi khai báo, thay vì để chúng là null cho tới "sau này".

Khi một giá trị thực sự có thể vắng mặt, như một lần tra cứu có thể hợp lệ mà không tìm thấy gì, Java có Optional, một kiểu chứa buộc bên gọi phải xử lý trường hợp "không có" thay vì lặng lẽ nhận về một null. Đó là khái niệm liên quan nên đọc tiếp nếu bạn muốn loại bỏ hẳn những lỗ hổng này khỏi API của mình.

Tổng kết

NullPointerException là cách Java báo cho bạn biết bạn đã dùng một tham chiếu đang giữ null như thể nó giữ một đối tượng. Cách sửa hiếm khi là bắt nó, mà là đọc thông báo lỗi, lần ngược tới chỗ lẽ ra giá trị phải được gán, rồi hoặc đảm bảo giá trị khác null, hoặc bảo vệ chỗ bạn dùng nó. Dựa vào Objects.requireNonNull để báo lỗi sớm tại điểm đầu vào, ưu tiên giá trị rỗng và getOrDefault thay cho null, và dùng Optional khi việc vắng mặt là một kết quả có thật, được dự tính trước. Nắm vững tư duy đó, lỗi phổ biến nhất trong Java sẽ trở thành một trong những lỗi hiếm gặp nhất trong code của bạn.

Câu hỏi thường gặp

Nguyên nhân gây ra NullPointerException trong Java là gì?

Lỗi này xảy ra khi bạn dùng một tham chiếu đang trỏ tới null như thể nó trỏ tới một đối tượng thật, chẳng hạn gọi phương thức (name.length()), đọc một trường, truy cập phần tử mảng, hoặc unboxing một Integer có giá trị null. Biến đó không chứa đối tượng nào, nên không có gì để thao tác và JVM ném ra NullPointerException.

Làm sao sửa lỗi NullPointerException trong Java?

Đọc thông báo lỗi. Từ Java 14, thông báo nêu chính xác thứ gì là null (ví dụ "Cannot invoke "String.length()" because "name" is null"). Sau đó lần ngược xem vì sao biến đó là null: thiếu bước khởi tạo, một phương thức trả về null, hoặc tra cứu map không tìm thấy khóa. Sửa tận gốc để giá trị không bao giờ là null, hoặc bảo vệ chỗ dùng bằng một phép kiểm tra null, Objects.requireNonNull hay Optional.

Nên kiểm tra null hay bắt NullPointerException?

Hãy kiểm tra null. NullPointerException báo hiệu một bug trong logic, không phải một tình huống được dự tính trước, nên bạn cần ngăn nó xảy ra thay vì bắt nó. Bắt lỗi này chỉ che đi chỗ thật sự có vấn đề. Hãy để dành try/catch cho những tình huống thật sự bất thường như lỗi I/O.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu