Nullable var và smart cast
Hãy xem xét khối sau đây của Kotlin.
var nullableInt: Int? = null
if (nullableInt != null) {
val checkedInt: Int = nullableInt
print("not-null-branch")
} else {
print("null-branch")
}
Android Studio cho tôi biết rằng dàn diễn viên thông minh từ Int?đến Intlà không thể, vì nullableIntcó thể thay đổi. Tôi hiểu rằng đây có thể là sự cố trong mã đa luồng.
Một cách để xử lý vấn đề là tạo một kết hợp rõ ràng với val checkedInt: Int = nullableInt!!, nhưng nếu tôi sử dụng mã hóa trong môi trường đa luồng thì không nên.
Đóng các bản sao
Có một số câu hỏi rất gần gũi trên SO liên quan đến chủ đề này. Tuy nhiên, tôi không tìm thấy câu trả lời phù hợp trong bất kỳ câu trả lời nào mà tôi đã tìm thấy:
Trong Kotlin, cách thành ngữ để xử lý các giá trị nullable là gì, việc tham chiếu hoặc chuyển đổi chúng sẽ thảo luận về lý do tại sao vấn đề phát sinh, nhưng không đưa ra gợi ý về cách xử lý nó.
Kotlin tránh ép kiểu thông minh để kiểm tra null có một nhánh if-not-null trả về giá trị không phải null, vì vậy ?.let{} :? {}cấu trúc hoạt động ở đó. Vì nhánh not-null của tôi trả về null, nên cả hai nhánh sẽ chạy.
Kotlin "Truyền thông minh là không thể, vì thuộc tính có thể đã được thay đổi vào thời điểm này" chỉ liên quan đến nhánh không-null và không nhánh null, vì vậy ?.let{}cấu trúc có vẻ đúng. Trong chuỗi đó, họ cung cấp gợi ý về việc lấy một bản sao cục bộ trước ifcâu lệnh, điều này cũng có thể làm được trong trường hợp của tôi. Thật không may, nó không phải là rất thanh lịch, và tôi hy vọng có một số thay thế khác.
Có cách nào để xử lý phân nhánh có điều kiện rỗng này theo cách an toàn không mà không cần lấy bản sao không?
Tôi hiểu rằng câu trả lời có thể là "nó phụ thuộc". Nếu đúng như vậy, xin hãy nói như vậy và giải thích tại sao.
Trả lời
Sử dụng .letthay vì?.let
Bởi vì .lethàm mở rộng được xác định cho tất cả các loại, bao gồm cả các hàm có thể vô hiệu, bạn thực sự có thể gọi nó mà không cần người ?.điều hành cuộc gọi an toàn . Khi bạn làm điều đó, lambda sẽ luôn được gọi, ngay cả đối với nullcác giá trị. Tham số bên trong letkhối sẽ là nullable nếu bộ nhận là nullable.
Tuy nhiên, tham số lambda là đủ điều kiện cho đúc thông minh , bởi vì nó không phải là có thể thay đổi.
Đây là sự khác biệt:
x.let { it -> /* This always runs. 'it' can be null if 'x' is null */ }
x?.let { it -> /* This only runs if 'x' is not null. 'it' is never null. */ }
Áp dụng điều đó vào ví dụ của bạn, bạn có thể viết như sau:
var nullableInt: Int? = null
nullableInt.let {
if (it != null) {
doSomethingWith(it)
} else {
doSomethingElse()
}
}
Bạn không thể bắt đầu truyền một thuộc tính vì một luồng khác có thể đang sửa đổi nó. Không có cách hợp lý nào xung quanh điều đó. Ngôn ngữ đã cung cấp một cách không an toàn để làm điều đó với ngôn ngữ !!mà bạn đã đề cập.
Việc tạo một bản sao cục bộ của tham chiếu (bằng cách thủ công hoặc bằng cách sử dụng một hàm phạm vi như withhoặc let) là điều không cần thiết nên bạn không cần phải lo lắng.
Ý kiến cá nhân của tôi là một biến cục bộ là cách tốt nhất, dễ đọc nhất để làm điều đó. Bạn tránh lồng ghép các khối mà bạn có với các hàm phạm vi.
val myVar = myProp
if (myVar != null) {
} else {
}
Tôi nghĩ rằng cách sạch sẽ nhất với các hàm phạm vi là sử dụng with. Nó đọc tốt hơn letkhi bạn đang xử lý cả hai nhánh.
with(myProp) {
if (this != null) {
} else {
}
}
Để có một cách ngắn gọn để xử lý cả hai nhánh, các công việc sau đây. Tôi coi alsolà mạnh mẽ hơn một chút so letvới tình huống này, bởi vì bạn không thể vô tình chạy cả hai nhánh bằng cách trả về null từ lambda đầu tiên. Nhưng khả năng đọc bị ảnh hưởng bởi sự ngắn gọn này.
myProp?.also {
// not null it
} ?: run {
// null
}