Trước tiên, bạn nên đọc tất cả về Null Safety trong Kotlin bao gồm các trường hợp kỹ lưỡng.
Trong Kotlin, bạn không thể truy cập giá trị null mà không chắc chắn là không null
( Kiểm tra null trong điều kiện ) hoặc khẳng định rằng nó chắc chắn không null
sử dụng !!
toán tử chắc chắn , truy cập bằng ?.
Cuộc gọi an toàn hoặc cuối cùng đưa ra một cái gì đó có thể null
là giá trị mặc định bằng cách sử dụng ?:
toán tử Elvis .
Đối với trường hợp đầu tiên trong câu hỏi của bạn, bạn có các tùy chọn tùy thuộc vào mục đích của mã bạn sẽ sử dụng một trong những điều này và tất cả đều là thành ngữ nhưng có kết quả khác nhau:
val something: Xyz? = createPossiblyNullXyz()
// access it as non-null asserting that with a sure call
val result1 = something!!.foo()
// access it only if it is not null using safe operator,
// returning null otherwise
val result2 = something?.foo()
// access it only if it is not null using safe operator,
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue
// null check it with `if` expression and then use the value,
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
something.foo()
} else {
...
differentValue
}
// null check it with `if` statement doing a different action
if (something != null) {
something.foo()
} else {
someOtherAction()
}
Đối với "Tại sao nó hoạt động khi kiểm tra null", hãy đọc thông tin cơ bản bên dưới trên phôi thông minh .
Đối với trường hợp thứ 2 trong câu hỏi của bạn trong câu hỏi với Map
, nếu bạn là nhà phát triển chắc chắn về kết quả không bao giờ xảy ra null
, !!
hãy sử dụng toán tử chắc chắn làm xác nhận:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
hoặc trong một trường hợp khác, khi COULD bản đồ trả về null nhưng bạn có thể cung cấp một giá trị mặc định, thì Map
chính nó có một getOrElse
phương thức :
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid
Thông tin lai lịch:
Lưu ý: trong các ví dụ dưới đây tôi đang sử dụng các loại rõ ràng để làm cho hành vi rõ ràng. Với suy luận kiểu, thông thường các loại có thể được bỏ qua cho các biến cục bộ và các thành viên riêng.
Thông tin thêm về !!
toán tử chắc chắn
Các !!
nhà điều hành khẳng định rằng giá trị không phải là null
hay ném một NPE. Điều này nên được sử dụng trong trường hợp nhà phát triển đảm bảo rằng giá trị sẽ không bao giờ null
. Hãy nghĩ về nó như một sự khẳng định theo sau bởi một diễn viên thông minh .
val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!!
// same thing but access members after the assertion is made:
possibleXyz!!.foo()
đọc thêm: !! Nhà điều hành chắc chắn
Tìm hiểu thêm về null
Kiểm tra và thông minh
Nếu bạn bảo vệ quyền truy cập vào một loại nullable bằng một null
kiểm tra, trình biên dịch sẽ thông minh bỏ giá trị trong phần thân của câu lệnh thành không nullable. Có một số dòng phức tạp trong đó điều này không thể xảy ra, nhưng đối với các trường hợp phổ biến hoạt động tốt.
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
Hoặc nếu bạn thực hiện is
kiểm tra loại không thể rỗng:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
Và tương tự cho các biểu thức 'khi' cũng diễn viên an toàn:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
// or
when (possibleXyz) {
is Xyz -> possibleXyz.foo()
is Alpha -> possibleXyz.dominate()
is Fish -> possibleXyz.swim()
}
Một số điều không cho phép null
kiểm tra đúc thông minh cho lần sử dụng sau của biến. Ví dụ trên sử dụng một biến địa phương mà không có cách nào có thể biến đổi trong dòng chảy của các ứng dụng, cho dù val
hay var
biến này đã không có cơ hội để biến đổi thành một null
. Nhưng, trong các trường hợp khác mà trình biên dịch không thể đảm bảo phân tích luồng, đây sẽ là một lỗi:
var nullableInt: Int? = ...
public fun foo() {
if (nullableInt != null) {
// Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
val nonNullableInt: Int = nullableInt
}
}
Vòng đời của biến nullableInt
không hoàn toàn hiển thị và có thể được chỉ định từ các luồng khác, null
kiểm tra không thể được đúc thông minh thành một giá trị không thể rỗng. Xem chủ đề "Cuộc gọi an toàn" bên dưới để biết cách giải quyết.
Một trường hợp khác không thể tin tưởng bởi một diễn viên thông minh để không đột biến là một thuộc val
tính trên một đối tượng có một getter tùy chỉnh. Trong trường hợp này, trình biên dịch không có khả năng hiển thị những gì làm thay đổi giá trị và do đó bạn sẽ nhận được thông báo lỗi:
class MyThing {
val possibleXyz: Xyz?
get() { ... }
}
// now when referencing this class...
val thing = MyThing()
if (thing.possibleXyz != null) {
// error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
thing.possiblyXyz.foo()
}
đọc thêm: Kiểm tra null trong điều kiện
Tìm hiểu thêm về ?.
toán tử cuộc gọi an toàn
Toán tử cuộc gọi an toàn trả về null nếu giá trị bên trái là null, nếu không thì tiếp tục đánh giá biểu thức ở bên phải.
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
Một ví dụ khác mà bạn muốn lặp lại một danh sách nhưng chỉ khi không null
và không trống, một lần nữa toán tử cuộc gọi an toàn lại có ích:
val things: List? = makeMeAListOrDont()
things?.forEach {
// this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
Trong một trong những ví dụ ở trên, chúng tôi đã có một trường hợp chúng tôi đã if
kiểm tra nhưng có cơ hội một luồng khác làm thay đổi giá trị và do đó không có diễn viên thông minh . Chúng ta có thể thay đổi mẫu này để sử dụng toán tử cuộc gọi an toàn cùng với let
chức năng để giải quyết điều này:
var possibleXyz: Xyz? = 1
public fun foo() {
possibleXyz?.let { value ->
// only called if not null, and the value is captured by the lambda
val surelyXyz: Xyz = value
}
}
đọc thêm: Cuộc gọi an toàn
Thông tin thêm về ?:
Nhà điều hành Elvis
Toán tử Elvis cho phép bạn cung cấp một giá trị thay thế khi một biểu thức ở bên trái của toán tử là null
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
Nó cũng có một số cách sử dụng sáng tạo, ví dụ như ném một ngoại lệ khi một cái gì đó là null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
hoặc để trở về sớm từ một chức năng:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
đọc thêm: Nhà điều hành Elvis
Toán tử Null với các chức năng liên quan
Kotlin stdlib có một loạt các chức năng hoạt động thực sự độc đáo với các toán tử được đề cập ở trên. Ví dụ:
// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething
// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
func1()
func2()
}
// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName
val something = name.takeUnless { it.isBlank() } ?: defaultName
Chủ đề liên quan
Trong Kotlin, hầu hết các ứng dụng đều cố gắng tránh null
các giá trị, nhưng không phải lúc nào cũng có thể. Và đôi khi null
làm cho ý nghĩa hoàn hảo. Một số hướng dẫn để suy nghĩ về:
trong một số trường hợp, nó đảm bảo các kiểu trả về khác nhau bao gồm trạng thái của lệnh gọi phương thức và kết quả nếu thành công. Các thư viện như Kết quả cung cấp cho bạn loại kết quả thành công hoặc thất bại cũng có thể phân nhánh mã của bạn. Và thư viện Promising cho Kotlin có tên Kovenant cũng thực hiện tương tự dưới dạng lời hứa.
đối với các bộ sưu tập dưới dạng các kiểu trả về luôn trả về một bộ sưu tập trống thay vì a null
, trừ khi bạn cần trạng thái thứ ba là "không có mặt". Kotlin có các hàm trợ giúp như emptyList()
hoặcemptySet()
để tạo các giá trị trống này.
khi sử dụng các phương thức trả về giá trị nullable mà bạn có mặc định hoặc thay thế, hãy sử dụng toán tử Elvis để cung cấp giá trị mặc định. Trong trường hợp Map
sử dụng getOrElse()
, cho phép tạo ra giá trị mặc định thay vì Map
phương thức get()
trả về giá trị null. Giống vớigetOrPut()
khi ghi đè các phương thức từ Java trong đó Kotlin không chắc chắn về tính không hợp lệ của mã Java, bạn luôn có thể loại bỏ ?
tính không hợp lệ khỏi ghi đè của mình nếu bạn chắc chắn chữ ký và chức năng phải là gì. Do đó phương pháp ghi đè của bạn là null
an toàn hơn . Tương tự như vậy đối với việc triển khai các giao diện Java trong Kotlin, thay đổi tính không hợp lệ thành những gì bạn biết là hợp lệ.
nhìn vào các chức năng có thể giúp đỡ, chẳng hạn như String?.isNullOrEmpty()
và String?.isNullOrBlank()
có thể hoạt động trên một giá trị nullable một cách an toàn và làm những gì bạn mong đợi. Trên thực tế, bạn có thể thêm các tiện ích mở rộng của riêng mình để điền vào bất kỳ khoảng trống nào trong thư viện chuẩn.
chức năng khẳng định như checkNotNull()
và requireNotNull()
trong thư viện tiêu chuẩn.
các hàm trợ giúp như filterNotNull()
loại bỏ null khỏi các bộ sưu tập hoặc listOfNotNull()
để trả về một danh sách mục không hoặc đơn từ một null
giá trị có thể .
cũng có một toán tử cast An toàn (nullable) cũng cho phép chuyển kiểu thành không thể null trở về null nếu không thể. Nhưng tôi không có trường hợp sử dụng hợp lệ cho trường hợp này không được giải quyết bằng các phương pháp khác được đề cập ở trên.