繁体   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