Không thể thay đổi thông minh thành 'Loại', vì 'biến' là thuộc tính có thể thay đổi có thể được thay đổi vào thời điểm này


275

Và người mới của Kotlin hỏi, "tại sao sẽ không biên dịch mã sau đây?":

    var left: Node? = null

    fun show() {
         if (left != null) {
             queue.add(left) // ERROR HERE
         }
    }

Không thể thay đổi thông minh thành 'Nút', vì 'trái' là thuộc tính có thể thay đổi có thể được thay đổi vào thời điểm này

Tôi nhận được đó leftlà biến có thể thay đổi, nhưng tôi đang kiểm tra rõ ràng left != nullleftthuộc loại Nodevì vậy tại sao nó không thể được đúc thông minh cho loại đó?

Làm thế nào tôi có thể sửa lỗi này một cách thanh lịch? :)


3
Một nơi nào đó ở giữa một luồng khác nhau có thể đã thay đổi giá trị thành null một lần nữa. Tôi khá chắc chắn câu trả lời cho các câu hỏi khác cũng đề cập đến điều đó.
nhaarman

3
Bạn có thể sử dụng một cuộc gọi an toàn để thêm
Whymarrh

cảm ơn @nhaarman có ý nghĩa, Whymarrh làm thế nào để làm điều đó? Tôi nghĩ rằng các cuộc gọi an toàn chỉ dành cho các đối tượng không phải là phương thức
FRR

6
Một cái gì đó như: n.left?.let { queue.add(it) }tôi nghĩ?
Jorn Vernee

Câu trả lời:


356

Giữa thực hiện left != nullqueue.add(left)chủ đề khác có thể đã thay đổi giá trị của leftđể null.

Để làm việc xung quanh điều này, bạn có một số tùy chọn. Đây là một số:

  1. Sử dụng một biến cục bộ với diễn viên thông minh:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Sử dụng một cuộc gọi an toàn, chẳng hạn như một trong những điều sau đây:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Sử dụng các toán tử Elvis với returntrở lại sớm từ chức năng kèm theo:

    queue.add(left ?: return)

    Lưu ý rằng breakcontinuecó thể được sử dụng tương tự để kiểm tra trong các vòng lặp.


8
4. Nghĩ về một giải pháp chức năng hơn cho vấn đề của bạn mà không yêu cầu các biến có thể thay đổi.
Chúc ngủ ngon Nerd Pride

1
@sak Đó là một thể hiện của một Nodelớp được định nghĩa trong phiên bản gốc của câu hỏi có đoạn mã phức tạp hơn n.leftthay vì đơn giản left. Tôi đã cập nhật câu trả lời tương ứng. Cảm ơn.
mfulton26

1
@sak Các khái niệm tương tự được áp dụng. Bạn có thể tạo một cái mới valcho mỗi var, lồng một số ?.letcâu lệnh hoặc sử dụng một số ?: returncâu lệnh tùy thuộc vào chức năng của bạn. ví dụ MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Bạn cũng có thể thử một trong các giải pháp cho "nhiều biến cho phép" .
mfulton26

1
@FARID ai đang đề cập đến?
mfulton26

3
Vâng, nó an toàn. Khi một biến được khai báo là lớp toàn cầu, bất kỳ luồng nào cũng có thể sửa đổi giá trị của nó. Nhưng trong trường hợp một biến cục bộ (một biến được khai báo bên trong hàm), biến đó không thể truy cập được từ các luồng khác, vì vậy an toàn để sử dụng.
Farid

31

1) Ngoài ra, bạn có thể sử dụng lateinitNếu bạn chắc chắn thực hiện việc khởi tạo của mình sau này onCreate()hoặc ở nơi khác.

Dùng cái này

lateinit var left: Node

Thay vì cái này

var left: Node? = null

2) Và có một cách khác là sử dụng !!kết thúc biến khi bạn sử dụng nó như thế này

queue.add(left!!) // add !!

nó làm gì?
c-an

@ c-an nó làm cho biến của bạn khởi tạo thành null nhưng chắc chắn sẽ khởi tạo sau trong mã.
Radesh

Sau đó, nó không giống nhau? @Radesh
c-an

@ c-an giống với cái gì?
Radesh

1
Tôi đã trả lời câu hỏi ở trên, đó là Smart cast thành 'Node' là không thể, bởi vì 'left' là một thuộc tính có thể thay đổi có thể được thay đổi vào thời điểm này mã này ngăn chặn lỗi đó bằng cách chỉ định loại biến. vì vậy trình biên dịch không cần diễn viên thông minh
Radesh

27

Có một lựa chọn thứ tư ngoài những lựa chọn trong câu trả lời của mfulton26.

Bằng cách sử dụng ?.toán tử, có thể gọi các phương thức cũng như các trường mà không cần xử lý lethoặc sử dụng các biến cục bộ.

Một số mã cho bối cảnh:

var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called

Nó hoạt động với các phương thức, các trường và tất cả những thứ khác mà tôi đã cố gắng để nó hoạt động.

Vì vậy, để giải quyết vấn đề, thay vì phải sử dụng phôi thủ công hoặc sử dụng biến cục bộ, bạn có thể sử dụng ?.để gọi các phương thức.

Để tham khảo, điều này đã được thử nghiệm trong Kotlin 1.1.4-3, nhưng cũng đã được thử nghiệm trong 1.1.511.1.60. Không có gì đảm bảo nó hoạt động trên các phiên bản khác, nó có thể là một tính năng mới.

Sử dụng ?.toán tử không thể được sử dụng trong trường hợp của bạn vì đó là một biến được thông qua đó là vấn đề. Toán tử Elvis có thể được sử dụng thay thế và có lẽ đây là toán tử yêu cầu số lượng mã ít nhất. Thay vì sử dụng continuemặc dù, returncũng có thể được sử dụng.

Sử dụng đúc thủ công cũng có thể là một tùy chọn, nhưng điều này không an toàn:

queue.add(left as Node);

Có nghĩa là nếu trái đã thay đổi trên một chủ đề khác, chương trình sẽ sụp đổ.


Theo tôi hiểu, '?' toán tử đang kiểm tra xem biến ở phía bên trái của nó có phải không .. Trong ví dụ trên, nó sẽ là 'hàng đợi'. Lỗi 'không thể truyền thông minh' đang đề cập đến tham số "trái" được truyền vào phương thức "thêm" ... Tôi vẫn gặp lỗi nếu tôi sử dụng phương pháp này
FRR

Đúng, lỗi là trên leftvà không queue. Cần kiểm tra điều này, sẽ chỉnh sửa câu trả lời trong một phút
Zoe

4

Lý do thực tế tại sao điều này không hoạt động không liên quan đến chủ đề. Vấn đề là node.leftđược dịch thành hiệu quả node.getLeft().

Trình nhận thuộc tính này có thể được định nghĩa là:

val left get() = if (Math.random() < 0.5) null else leftPtr

Do đó, hai cuộc gọi có thể không trả về cùng một kết quả.


2

Thay đổi var left: Node? = nullthành lateinit var left: Node. Vấn đề được giải quyết.


1

Làm cái này:

var left: Node? = null

fun show() {
     val left = left
     if (left != null) {
         queue.add(left) // safe cast succeeds
     }
}

Đây dường như là lựa chọn đầu tiên được cung cấp bởi câu trả lời được chấp nhận, nhưng đó là những gì bạn đang tìm kiếm.


Đây là bóng của biến "trái"?
AFD

Mà là hoàn toàn ok. Xem reddit.com/r/androiddev/comments/fdp2zq/
Kẻ

1

Để có một Cast thông minh của các thuộc tính, kiểu dữ liệu của thuộc tính phải là lớp có chứa phương thức hoặc hành vi mà bạn muốn truy cập và KHÔNG phải là thuộc tính đó thuộc loại siêu hạng.


ví dụ: trên Android

Là:

class MyVM : ViewModel() {
    fun onClick() {}
}

Giải pháp:

From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM

Sử dụng:

viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}

GL


1

Giải pháp thanh lịch nhất của bạn phải là:

var left: Node? = null

fun show() {
    left?.also {
        queue.add( it )
    }
}

Sau đó, bạn không phải xác định một biến cục bộ mới và không cần thiết và bạn không có bất kỳ xác nhận hoặc phôi mới nào (không phải là DRY). Các chức năng phạm vi khác cũng có thể hoạt động để chọn yêu thích của bạn.


0

Hãy thử sử dụng toán tử xác nhận không null ...

queue.add(left!!) 

3
Nguy hiểm. Vì lý do tương tự, việc tự động truyền không hoạt động.
Jacob Zimmerman

3
Nó có thể gây ra sự cố ứng dụng nếu trái là null.
Pritam Karmakar

0

Làm thế nào tôi sẽ viết nó:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.