簡體   English   中英

Null 檢查與? vs?:在 kotlin 中失敗

[英]Null check with ?. vs ?: fails in kotlin

我的代碼中有以下語句:

safeOrderResult.accomplished?.let{ safeAccomplished->
  //Do something with safeAccomplished when accomplished <> null

  Log.i(TAG,"bind: safeOrderResult.accomplishedId.let?{}") 
}?:let{

  //Do something when accomplished == null

  Log.i(TAG,"bind: safeOrderResult.accomplishedId?:let{} *null*" )
}

在這里,我的代碼做了一些奇怪的事情:

在三星 TAB A(我認為不重要)上,它按預期工作。

在三星 S9 上,它調用了兩個 let 部分。

來自 Logcat 三星 S9 (android 10) 的片段

2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId = 408
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId.let?.{}
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: handleDate = null <- inside above let: ok 
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId?:let{} *null*
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: flagged = false or null

TabA:android 10

2021-05-06 14:21:16.676 2468-2468/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId = 427
2021-05-06 14:21:16.676 2468-2468/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId.let?.{}
2021-05-06 14:21:16.678 2468-2468/no.norva24.mslam I/ViewHolder: bind: handleDate = null <-- inside above let
2021-05-06 14:21:16.685 2468-2468/no.norva24.mslam I/ViewHolder: bind: flagged = false or null

關鍵是,一個值如何既是 null 又包含一個值?或者 kotlin 可以“更改”為 null 並在第二個“讓”中發生變化不做)

我正在使用 kotlin 1.5.0

編輯 2021.05.06 18:55 GMT+2

我不確定,但我今天可能在這里學到了一些東西:;)

safeOrderResult.accomplished?.let{ safeAccomplished->

  //Do something with safeAccomplished when accomplished <> null

  /*Here I have preserved my value in safeAccomplished
    And actually returning a value below (a Unit()) from Log.i ?*/
  Log.i(TAG,"bind: safeOrderResult.accomplishedId.let?{}") 
}?:let{
   
  //Do something when accomplished == null

  /* But why did the code kick in here ?
     After it was inside the let above ? I thought the '?:let' was 
     continuing if the '?.let' didn't kick in. 
     */
  Log.i(TAG,"bind: safeOrderResult.accomplishedId?:let{} *null*" )
}

/*
Below is the actual code which had the trouble (the code isn't finished therefore the "preserved" `let` values isn't used)
*/

                        safeOrderResult.accomplishedId?.let {
                            listItemOrderListLinearLayoutCompatStatus.apply {
                                visibility = View.VISIBLE
                                listItemOrderListMaterialTextViewOrderStatus.text =
                                    context.resources.getStringArray(
                                        R.array.basic_register_accomplish_status_names)[1]
                                listItemOrderListMaterialTextViewDate.text =
                                    dateStringSplitSpace(safeOrderResult.registeredDate)
                                Log.i(TAG, "bind: handleDate = ${safeOrderResult.handleDate}")
                                listItemOrderListMaterialTextViewReason.text =
                                    if(safeOrderResult.handleDate.isNullOrEmpty())
                                        "Still possible to update"
                                    else
                                        "Assignment locked on ${dateStringSplitSpace(safeOrderResult.handleDate)}"
                                setBackgroundColor(
                                    ContextCompat.getColor(
                                        itemView.context,
                                        if(safeOrderResult.handleDate.isNullOrEmpty())
                                            R.color.list_item_register_field_accomplished_background
                                        else
                                            R.color.list_item_register_field_accomplished_locked_background
                                    )
                                )

                            }
                            listItemOrderListLinearLayoutCompatStatusMore?.apply {
                                setBackgroundColor(
                                    ContextCompat.getColor(
                                        itemView.context,
                                        if(safeOrderResult.handleDate.isNullOrEmpty())
                                            R.color.list_item_register_field_accomplished_background
                                        else
                                            R.color.list_item_register_field_accomplished_locked_background
                                    )
                                )
                            }
                        }?:let {
                            safeOrderResult.passedId?.let { safePassedId->
                                listItemOrderListLinearLayoutCompatStatus.apply {
                                    visibility = View.VISIBLE
                                    listItemOrderListMaterialTextViewOrderStatus.text =
                                        context.resources.getStringArray(
                                            R.array.basic_register_accomplish_status_names
                                        )[2]
                                    listItemOrderListMaterialTextViewDate.text =
                                        dateStringSplitSpace(safeOrderResult.registeredDate)
                                    listItemOrderListMaterialTextViewReason.text =
                                        safeOrderResult.passedReason
                                    setBackgroundColor(
                                        ContextCompat.getColor(
                                            itemView.context,
                                            R.color.list_item_register_field_passed_background,
                                        )
                                    )
                                }
                            }?:let {
                                listItemOrderListLinearLayoutCompatStatus.apply {
                                    visibility = View.GONE
                                }
                            }
                        }

** 附錄 2020.05.06 19:30 GMT+2 **

在操場上,我遇到了麻煩:

/**
 * You can edit, run, and share this code. 
 * play.kotlinlang.org 
 */

class A {
    fun f() {
        let { println(it) }
    }
}

data class DataClass(
    var value1:String?=null,
    var value2:String?=null
)

fun main() {
    A().f()
    
    var myData = DataClass()
    
    myData.value1 = "1"
    
    
    myData.value1?.let{ safeValue1->
        println("value1 = "+safeValue1)
    }?:let{
        println("value1==null !!")
    }
    
    myData.value2?.let{ safeValue2->
        println("value2 = "+safeValue2)
    }?:let{
        println("value2==null !!")
    }   
    
    
}

它在?:let 's 上面踢到了哪里。 至少在 kotin v.1.5.0 中這是可以的......

附錄 2:2020.05.06 19:40 GMT+2

在此處輸入圖像描述

所以... dataClass.value?:let{ }是不允許的嗎? 在“標准”kotlin 方案中檢查 null 是否存在?,但在AS2020.3.1.15 w/kotlin 1.5.0中仍然“有效” ...

附錄 3:2020.05.06 19:55 GMT+2

當使用另一種方法時(在?:let{中省略let關鍵字,我根據上面的操場代碼得到了這個答案:

在此處輸入圖像描述

在這里,我還希望 value2 以value2==null !! 但它沒有...

現在是操場代碼:

/**
 * You can edit, run, and share this code. 
 * play.kotlinlang.org 
 */

class A {
    fun f() {
        let { println(it) }
    }
}

data class DataClass(
    var value1:String?=null,
    var value2:String?=null
)

fun main() {
    A().f()
    
    var myData = DataClass()
    
    myData.value1 = "1"
    
    /*
    myData.value1?.let{ safeValue1->
        println("value1 = "+safeValue1)
    }?:let{
        println("value1==null !!")
    }
    
    myData.value2?.let{ safeValue2->
        println("value2 = "+safeValue2)
    }?:let{
        println("value2==null !!")
    }   
    */
    
    myData.value1?.let{ safeValue1->
        println("value1 = "+safeValue1)
    }
    
    myData.value1?:{
        println("value1==null !!")
    }
    
    myData.value2?.let{ safeValue2->
        println("value2 = "+safeValue2)
    }
    
    myData.value2?:{
        println("value2==null !!")
    }
    
}

……還是有點糊塗……

let function 確實可以將您的目標更改為 null。 它將目標更改為它返回的任何內容。 lambda 隱式返回其最后一個表達式的結果。 上面的代碼有一個Log.i()調用作為它的最后一個表達式,所以它返回 Unit,所以如果第一個運行,第二個let function 不應該運行。 您是否有可能在第一次let lambda 結束時剪掉了一些可能返回 null 值的代碼?

對上述問題的快速解決方法是將let替換為also ,因為also總是返回它的接收者。

我認為最有經驗的 Kotlin 用戶會建議您不要鏈接 scope function 調用,因為它使代碼難以遵循並且很容易引入細微的錯誤。 您可以編寫更強大的版本,如下所示:

val accomplished = safeOrderResult.accomplished
if (accomplished != null) {
    //Do something with smart-cast non-nullable accomplished
} else {
    //Do something when accomplished == null
}

猜測一下,第一個在最后返回null ,這意味着整個表達式產生的值是null ,所以?:之后的東西被觸發(因為這是“如果左側評估為空”條件) .

為什么這只會發生在某些三星型號上 - 誰知道,他們有在 Android 庫中弄亂東西的歷史。 我會准確檢查塊中發生的事情以及它可能評估的內容。 您可能需要在最后返回Unit ,或使用 function(如apply )返回接收器而不是 lambda 的結果。

這就是為什么if/else更合適的原因——你在一開始就有一個條件,然后你決定是專門做一件事還是另一件事。 let產生一個值,它通常用於沿鏈傳播一個值,並返回一個結果。 ?:是最終的默認值,如果結果是 null。

絕對可以運行let?:之后的代碼,有時這是您想做的事情。 作為一種構造,它通常用於返回默認值。 所以if/else更明確地說明了你在做什么以及它如何工作的,它有助於避免像這樣的意外錯誤!

可以?.let做一個 if-null-else 但在我看來它不是很可讀

var s: String? = "Str"
s?.let { println("A ok") } ?: run { println("A null") }
s = null
s?.let { println("B ok") } ?: run { println("B null") }
A ok
B null

也可以引入像這樣的細微錯誤:

var s: String? = "Str"
s?.let { println("A ok"); null } ?: run { println("A null") }
A ok
A null

這就是為什么如果您都需要非空和 null 案例,您應該使用 if-else ?.適用於只有非空情況才有意義的情況):

if (s == null) println("A null") else println("A ok")
if (s == null) { 
    println("A null") 
} else { 
    println("A ok") 
}

如果您不想像@Tenfour04 的回答那樣將accomplished綁定到變量,我會將其寫為

safeOrderResult.accomplished.let {
    if (it != null) {
        // safeOrderResult.accomplished was not null, use it
    } else {
        // safeOrderResult.accomplished was null
    }
}

或者

safeOrderResult.accomplished.let { accomplished ->
    if (accomplished != null) {
        // safeOrderResult.accomplished was not null, use accomplished
    } else {
        // safeOrderResult.accomplished was null
    }
}

注意.let而不是?.let 但是選擇可讀性/方便性。 我絕對不會用

value?.let{ safeValue-> /*dowork*/}; value?:let{/*do null work*/}

正如您在另一條評論中所建議的那樣。

感謝上面的許多好答案,你們都是對的......

我為我的問題找到了以下解決方案,但仍然不太高興:

我使用.apply刪除一些value. 高架,

safeOrderResult.apply{

   if(accomplished!=null){
        //Do something with accomplished since accomplished <> null

        Log.i(TAG,"bind: accomplished != null") 
   }else{

        //Do something when accomplished == null

        Log.i(TAG,"bind: accomplished == null" )
   }
}

我標記接受@Alexey Romanov 的建議,這是非常合理的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM