-3

Nói tạm biệt với NullPointerException trong Java

Lập trình viên Java từ lúc bắt đầu đã quá quen thuộc với lỗi Null Pointer Exception (NPE). Hầu hết các trường hợp ngoại lệ NPE đều dễ dàng tìm được ra lỗi, nhưng với các ứng dụng lớn mức độ doanh nghiệp có đến hàng trăm class khác nhau, NPE trở thành cơn ác mộng đích thực. NullPointerException là gì?

NullPointerException (NPE) là lỗi ngoại lệ xảy ra khi lập trình viên tham chiếu tới Object nhưng nó lại không có vị trí nào trên bộ nhớ (null). Gọi một method tham chiếu null hoặc cố gắng truy cập một trường tham chiếu null là một trong những nguyên nhân phổ biến gây ra lỗi Null Pointer Exception.

  • Theo JavaDoc, NPE được gây ra bởi những nguyên nhân:

  • Ném null như kiểu nó là một giá trị Throwable.

  • Gọi một Instance method của một null object.

  • Truy cập hoặc tùy chỉnh trường giá trị của null object.

  • Lấy độ dài của Null như một mảng.

  • Truy cập hoặc tùy chỉnh giá trị Null như kiểu một mảng.

  • Làm cách nào để thực sự phòng tránh được lỗi java.lang.NullPointerException khi chạy chương trình?

Bài viết sẽ điểm qua một vài ví dụ gây ra lỗi NPE và những cách giải quyết:

Chúng ta tạo lỗi NullPointerException theo 3 cách khác nhau:

  • Gây lỗi NPE nếu bạn cố gắng truy nhập null Object.

  • Gây lỗi NPE nếu bạn cố đổi null String.

  • Gây lỗi NPE nếu bạn cố truy nhập null Object trong quá trình khởi tạo Class.

Tạo class mẫu như dưới đây:

public class NullPointerExceptionTest {
	public static void main(String[] args) {
		try {
			
			// VD1: Lỗi NPE xảy ra nếu bạn cố truy nhập một null Object
			NPE1();
		} catch (NullPointerException e) {
			System.out.println("Exception in NPE1()");
			e.printStackTrace();
		}
 
		try {
			
			// VD2: Lỗi NPE xảy ra khi bạn có đổi một null String
		    NPE2();
		} catch (NullPointerException e) {
			System.out.println("\nException in NPE2()");
			e.printStackTrace();
		}
 
		try {
			//VD 3: Lỗi NPE xảy ra khi bạn cố truy nhập
			//null Object trong quá trình khởi tạo class
			
			NullTest npe = new NullTest();
			npe.getName();
		} catch (NullPointerException e) {
			System.out.println("\n Exception in NullNPETest()");
			e.printStackTrace();
		}
 
	}
 
	private static void NPE1() {
		Object object = null;
		object.hashCode();
	}
 
	private static void NPE2() {
		String str;

		//Chúng ta gọi một biến techmasterString có kiểu giá trị String
		//Biên chưa trỏ tới giá trị cụ thể, nên Java mặc định giá trị nó là null

		str = "Test text";

                //Sau dòng lệnh trên, biến được gọi giá trị cụ thể 
                //với giá trị cụ thể trên bộ nhớ
		// nên sẽ không bị lỗi NullPointerException


		System.out.println("\nvalue: " + str.toString() + ", lenght: " + str.length());
		System.out.println("No NPE exception");
 
		// Giờ tạo ra lỗi NullPointerException nào
		String str2 = null;
		System.out.println(str2.toString());
 
	}	
}
 
class NullTest {
	private String str;
 
	public void setName(String name) {
		this.str = name;
	}
 
	public void getName() {
		printName(str);
	}
 
	private void printName(String s) {
		System.out.println(s + " (" + s.length() + ")");
	}
}

Két quả như sau:

Exception in NPE1()
java.lang.NullPointerException
	at test.null.java.NullPointerExceptionTest.NPE1(NullPointerException.java:34)
	at test.nul.java.NullPointerExceptionTest.main(NullPointerException.java:7)

value: Test text, lenght: 20
No NPE exception

Exception in NPE2()
java.lang.NullPointerException
	at test.null.java.NullPointerExceptionTest.NPE2(NullPointerException.java:51)
	at test.null.java.NullPointerExceptionTest.main(NullPointerException.java:15)

 Exception in TechmasterNPETest()
java.lang.NullPointerException
	at test.null.java.NullPointerExceptionTest.printName(NullPointerException.java:68)
	at test.null.java.NullPointerExceptionTest.getName(NullPointerException.java:64)
	at test.null.java.NullPointerException.main(NullPointerException.java:24)

Dưới đây là một vài mẹo tránh lỗi NullPointerException:

#1: Sử dụng gợi ý trên IDE tránh lỗi

Thông báo thông dụng trên IDE sẽ có gợi ý tránh lỗi.

#2: Tạo một methode kiểm tra Object.

Bổ sung method vào class NullPointerExceptionTest.java

public static boolean isNullOrEmpty(String str) {
		if (str == null)
			return true;
		else if (str.trim().equals(""))
			return true;
		else
			return false;
	}

Ta sửa lại code lại methode NPE2() trong class chính.

        String str = null;
		System.out.println(str.toString());
		
		if (!techmasterIsNullOrEmpty(str)) {
			System.out.println(str.toString());
		} else {
			System.out.println("str có giá trị null");
		}

#3: Kiểm tra String null sau lệnh thực thi trim()

public static boolean isNullOrEmptyAfterTrim(String str) {
        return (techmasterString == null || str.trim().length() == 0);
}

#4: Luôn sử dụng cấu trúc Try Catch bắt lỗi NullPointerException

try {
	NPE1();
} catch (NullPointerException e) {
	System.out.println("Exception in CrunchifyNPE1()" + e);
}

#5: Sử dụng Collections.emptyList()

Collections.emptyList()

#6: Sử dụng Java Assertions

// bổ sung thêm vào class chính
private void printName(String s) {
	assert (s != null) : "Name must be not null"; 
	System.out.println(s + " (" + s.length() + ")");
}

#7: Sử dụng containsKey(), containsValue(), contains() để kiểm tra giá trị null.

public class ContainKeyExample {
	public static void main(String args[]) {
		HashMap<Integer, String> map = new HashMap<Integer, String>();
 
		// Khởi tạo cách giá trị cho HashMap
		map.put(1, "test1");
		map.put(2, "test2");
		map.put(3, "test3");
		// kiểm tra sự tồn tại của key số 4
		if (map.containsKey(4)) {
			System.out.println("Nếu key 2 tồn tại thì: " + map.get(4));
		} else {
			System.out.println("Tránh được lỗi NullPointerException cho key 4");
		}
	}
}

Chạy thử class trên, bạn sẽ ra kết quả in chuỗi: "Tránh được lỗi NullPointerException cho key 4"

#8: Viết code thật khôn ngoan:

Hãy cùng xem hai đoạn code dưới đây:

public boolean checkName(String name) {
    return name.equalsIgnoreCase("Test doan text");
}

Khi biến name trong methode trên là null, chúng ta sẽ gặp lỗi NullPointerException.

Có thể khắc phục theo cách sau:

public boolean checkName(String name) {
    return "Test doan text".equalsIgnoreCase(name);
}

Chuỗi String "Test doan text" được đặt ở đầu là một đối tượng không bao giờ null nên khi chúng ta sử dụng nó để so sánh với giá trị của biến name, NullPointerException sẽ không bao giờ xảy ra.

Hãy luyện tập các ví dụ trên thử nhé, bạn sẽ tiết kiệm được kha khá thời gian trong tương lai khi làm chủ được lỗi NullPointerException đó.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.