Kotlin Bài 3: Null Safety – Kiểm tra biến null an toàn trong Kotlin

Chắc hẳn nếu bạn đã code qua Java thì bạn đã quá quen thuộc với dòng thông báo lỗi NullPointerException. Đây cũng là 1 lỗi hết sức phổ biến trong nhiều ngôn ngữ lập trình. Nó xuất hiện khi ta truy xuất đến 1 trường hoặc 1 phương thức của 1 biến hay đối tượng null (tức là biến hay đối tượng đó không có bất cứ 1 giá trị nào). Và Kotlin được thiết kế để giảm thiểu tối đa sự xuất hiện của sai lầm tỷ đô này. Hôm nay mình sẽ viết bài trình bày về cách kiểm tra biến null an toàn trong Kotlin. Cùng xem xem có gì hay ho ở đây nhé!

Non-null types – Kiểu dữ liệu không thể null và Nullable types – Kiểu dữ liệu có thể null

Non-null types – Kiểu dữ liệu không thể null

Như ở bài trước, mình đã giới thiệu về các kiểu dữ liệu và cách khai báo biến trong Kotlin, ta đã biết cách khai báo biến kèm theo giá trị khởi tạo:

Giờ ta thử gán cho biến name ở trên giá trị null xem sao:

Ta sẽ nhận đc ngay 1 thông báo lỗi Null cannot be a value of a non-null type String. Tức là giá trị null không thể được gán cho 1 biến có kiểu non-null type String. Ở bài trước, mình đã trình bày về các kiểu dữ liệu cơ bản. Tuy nhiên các kiểu dữ liệu đó đều là các kiểu non-null type, có nghĩa là không chấp nhận giá trị null. Ok, rắc rối thật đấy, không cho ta gán giá trị null thì ta thử không gán giá trị gì xem nào 😀

Giờ thì lại nhận đc thông báo thế này: variable ‘name’ must be initialized, tức là biến name phải được gán giá trị.

Vậy là bằng mọi giá, Kotlin ngăn cản ta định nghĩa 1 biến với giá trị null. Điều này có vẻ bất tiện nhưng thực ra rất an toàn, bởi khi ta không có biến null, đồng nghĩa với việc sẽ không dính vào lỗi NullPointerException đáng ghét. 1 tính năng rất cool mà Java không có đúng ko.

Nhưng chờ đã, trong thực tế, đâu phải mọi thứ đều nhất thiết phải có giá trị (khác null) đúng không. Đôi khi vẫn có những biến phải gán cho giá trị null (để giải phóng bộ nhớ chẳng hạn), hoặc những biến không phải trong trường hợp nào cũng truy xuất đến, chúng không nhất thiết phải có giá trị. Vậy để gán giá trị null cho 1 biến ta làm thế nào? Khi đó ta sẽ cần đên Nullable types – Kiểu dữ liệu có thể null.

Nullable types – Kiểu dữ liệu có thể null

Ta khai báo như sau:

Ta đã thêm 1 dấu ? vào sau kiểu dữ liệu String. Điều đó tức là biến name vẫn có kiểu là String, nhưng lúc này nó đã được phép mang giá trị null. Và lúc này trình biên dịch không còn báo lỗi nữa. Ta chạy chương trình và thu được kết quả  name = null hiển thị ở console. Và String? chính là 1 Nullable type, tức là các biến có kiểu này được phép mang giá trị null.

Vậy đúc kết lại, để khai báo các biến có thể mang giá trị null, ta dùng các kiểu dữ liệu Nullable type với cú pháp:

Safe call – Lời gọi an toàn

Đối với biến được khai báo với kiểu  Nullable type, khi truy xuất đến chúng, ta cần phải sử dụng Safe call – Lời gọi an toàn để đảm bảo chương trình ko xảy ra lỗi. Ví dụ ta muốn lấy độ dài của chuỗi name bên trên:

Ký tự ? được dùng để kiểm tra biến namenull hay không. Nếu name có giá trị,  ta lấy được giá trị của name.length và gán vào biến lengthOfName. Ngược lại, name.length sẽ không được truy xuất đến và biến lengthOfName sẽ có giá trị null. Lời gọi đến name?.length bên trên gọi là 1 safe call (lời gọi an toàn) vì nó sẽ không bao giờ gây lỗi NullPointerException, dù cho biến name có mang giá trị null. Kết quả của đoạn code trên sẽ là:

length = null

Thử gán name = “Nam” và chạy chương trình:

Ta sẽ thu được kết quả: length = 3

Nếu bỏ dấu ? trong lời gọi đến name?.length, trình biến dịch sẽ báo lỗi: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String. Tức là đối với các kiểu dữ liệu nullable (cho phép null), ta chỉ có thể dùng safe call như mình giới thiệu bên trên, hoặc sử dụng ký hiệu !! 

Vậy !! là cái gì? 

Toán tử !!

Nếu bạn chắc chắn biến name là khác null, có thể dùng !! để thay cho ?. Khi dùng !! thì dù cho namenull hay không, lời gọi đến name.length vẫn sẽ được gọi. Ta sẽ thử:

Kết quả: Length = 3

Nhưng khi name bị null thì sao:

Lúc này ta sẽ nhận được thông báo: Exception in thread “main” kotlin.KotlinNullPointerException. Đây chính là điều ta muốn tránh.

Mình không khuyến khích sử dụng !! vì nếu ở ví dụ trên, name mang giá trị null, ngoại lệ NullPointerException sẽ bị văng ra. Đôi khi ta chắc chắn 1 biến là not null nhưng vì lý do nào đó nó bị mang giá trị null, khi đó sử dụng !! sẽ rất nguy hiểm.

Thêm 1 cách để kiểm tra biến null an toàn

Như ở trên mình đã dùng ?.let để xác định xem biến namenull hay không, nếu ko null thì các câu lệnh trong khối lệnh {} mới được thực hiện. Và trong khối lệnh ta không cần phải thêm ký tự safe call (?) hay null-aserted (!!) để truy xuất đến giá trị name.length trong đối tượng name. Đơn giản vì nó đã được chứng nhận là an toàn sau từ khoá let.

Toán tử Elvis

Khi ta có 1 đối tượng str có thể mang giá trị null. Ta có thể sử dụng toán tử Elvis để kiểm tra giá trị của nó. Cách sử dụng như sau:

Câu lệnh trên có ý nghĩa là: Nếu biểu thức bên trái dấu ?: (trong trường hợp này là str?.length) khác null, thì biến length sẽ được gán giá trị của str?.length, ngược lại, biểu thức bên trái dấu ?: mang giá trị null, biến length sẽ được gán giá trị của biểu thức bên phải dấu ? (trong trường hợp này là -1). Chỉ đơn giản là vậy.

Tổng kết

Như vậy trong bài này mình đã giới thiệu với các bạn cách sử dụng safe call (lời gọi an toàn) đối với các đối tượng có kiểu nullable-type. Việc làm kiểm tra biến null này sẽ giúp các bạn giảm thiểu rất nhiều nguy cơ bị dính ngoại lệ NullPointerException, cũng tức là giảm tỉ lệ bị crash app khi bạn phát triển ứng dụng Android sau này.

 

Hãy chia sẻ

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *