简体   繁体   English

Null 检查与? vs?:在 kotlin 中失败

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

I have following statement in my code:我的代码中有以下语句:

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*" )
}

Here my code does something strange:在这里,我的代码做了一些奇怪的事情:

On a Samsung TAB A (i think not significant) it works as expected.在三星 TAB A(我认为不重要)上,它按预期工作。

On a Samsung S9 it calls both let sections.在三星 S9 上,它调用了两个 let 部分。

Snippet from Logcat Samsung S9 (android 10)来自 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 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

The key point is, how can a value both be null and contain a value?, or can kotlin "change" to null and kick in in the second "null" let, if value has changed in the first first let (which I didn't do)关键是,一个值如何既是 null 又包含一个值?或者 kotlin 可以“更改”为 null 并在第二个“让”中发生变化不做)

I am Using kotlin 1.5.0我正在使用 kotlin 1.5.0

EDIT 2021.05.06 18:55 GMT+2编辑 2021.05.06 18:55 GMT+2

I am not sure, but I might have learned something here today: ;)我不确定,但我今天可能在这里学到了一些东西:;)

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
                                }
                            }
                        }

** ADDENDUM 2020.05.06 19:30 GMT+2 ** ** 附录 2020.05.06 19:30 GMT+2 **

In playground I got trouble with this:在操场上,我遇到了麻烦:

/**
 * 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 !!")
    }   
    
    
}

Where it kicked on the ?:let 's above.它在?:let 's 上面踢到了哪里。 This was ok in kotin v.1.5.0 at least...至少在 kotin v.1.5.0 中这是可以的......

ADDENDUM 2: 2020.05.06 19:40 GMT+2附录 2:2020.05.06 19:40 GMT+2

在此处输入图像描述

So... dataClass.value?:let{ } isn't allowed?所以... dataClass.value?:let{ }是不允许的吗? in a 'standard' kotlin scenario to check for null existence?, but still 'valid' in AS2020.3.1.15 w/kotlin 1.5.0 ...在“标准”kotlin 方案中检查 null 是否存在?,但在AS2020.3.1.15 w/kotlin 1.5.0中仍然“有效” ...

ADDENDUM 3: 2020.05.06 19:55 GMT+2附录 3:2020.05.06 19:55 GMT+2

When using another approach (omitting let keyword in ?:let{ I got this answer to the based on the playground code above:当使用另一种方法时(在?:let{中省略let关键字,我根据上面的操场代码得到了这个答案:

在此处输入图像描述

Here I expected also the value2 to show up with value2==null !!在这里,我还希望 value2 以value2==null !! but it didn`t...但它没有...

Here's the playground code now:现在是操场代码:

/**
 * 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 !!")
    }
    
}

...still a little confused... ……还是有点糊涂……

The let function can indeed change your target to null. let function 确实可以将您的目标更改为 null。 It changes the target to whatever it returns.它将目标更改为它返回的任何内容。 A lambda implicitly returns the result of its last expression. lambda 隐式返回其最后一个表达式的结果。 Your code above has a Log.i() call as its last expression, so it returns Unit, so the second let function should never run if the first one does.上面的代码有一个Log.i()调用作为它的最后一个表达式,所以它返回 Unit,所以如果第一个运行,第二个let function 不应该运行。 Is it possible you've snipped off some code at the end of your first let lambda that could possibly return a null value?您是否有可能在第一次let lambda 结束时剪掉了一些可能返回 null 值的代码?

A quick fix for the above problem would be to swap let for also , because also always returns its receiver.对上述问题的快速解决方法是将let替换为also ,因为also总是返回它的接收者。

I think most experienced Kotlin users will advise you not to chain scope function calls like this because it makes the code hard to follow and it is easy to introduce subtle bugs.我认为最有经验的 Kotlin 用户会建议您不要链接 scope function 调用,因为它使代码难以遵循并且很容易引入细微的错误。 You can write a more robust version like this:您可以编写更强大的版本,如下所示:

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

At a guess, the first one is returning null at the end, which means the value produced by that whole expression is null , so the stuff after the ?: is triggered (since that's an "if the left side evaluates to null" condition).猜测一下,第一个在最后返回null ,这意味着整个表达式产生的值是null ,所以?:之后的东西被触发(因为这是“如果左侧评估为空”条件) .

Why that would only happen on some Samsung models - who knows, they have a history of messing with things in the Android library.为什么这只会发生在某些三星型号上 - 谁知道,他们有在 Android 库中弄乱东西的历史。 I'd check exactly what's going on in the block and what it might evaluate to.我会准确检查块中发生的事情以及它可能评估的内容。 You might need to return Unit at the end, or use a function like apply that returns the receiver instead of the result of the lambda.您可能需要在最后返回Unit ,或使用 function(如apply )返回接收器而不是 lambda 的结果。

This is why the if/else is a better fit - you have a condition at the start, and you decide whether to do one thing or another, exclusively.这就是为什么if/else更合适的原因——你在一开始就有一个条件,然后你决定是专门做一件事还是另一件事。 let produces a value, and it's often used to propagate a value down a chain, and return a result. let产生一个值,它通常用于沿链传播一个值,并返回一个结果。 ?: is a final default value, for if that result turns out to be null. ?:是最终的默认值,如果结果是 null。

It's absolutely possible to run the let block and the code after the ?: , and sometimes that a thing you want to do.绝对可以运行let?:之后的代码,有时这是您想做的事情。 As a construction it's often used for returning a default value.作为一种构造,它通常用于返回默认值。 So if/else is a little more explicit about what you're doing and how it's meant to work, and it helps avoid surprise bugs like this one!所以if/else更明确地说明了你在做什么以及它如何工作的,它有助于避免像这样的意外错误!

You can do an if-null-else with ?.let but it's not very readable in my opinion可以?.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

It is also possible to introduce subtle bugs like this:也可以引入像这样的细微错误:

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

This is why you should use an if-else if you both need the non-null and null case.这就是为什么如果您都需要非空和 null 案例,您应该使用 if-else ( ?. is intended for the case where only the non-null case makes sense): ?.适用于只有非空情况才有意义的情况):

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

If you don't want to bind accomplished to a variable as in @Tenfour04's answer, I'd write it as如果您不想像@Tenfour04 的回答那样将accomplished绑定到变量,我会将其写为

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

or或者

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

Note .let and not ?.let .注意.let而不是?.let But pick on readability/convenience.但是选择可读性/方便性。 I definitely wouldn't use我绝对不会用

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

as you suggest in another comment.正如您在另一条评论中所建议的那样。

Thanx for many good answers above, and you all are right...感谢上面的许多好答案,你们都是对的......

I landed on following solution for my problem, but still not quite happy though:我为我的问题找到了以下解决方案,但仍然不太高兴:

I use .apply to remove some value.我使用.apply删除一些value. overhead,高架,

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" )
   }
}

I mark accepted for @Alexey Romanov suggestion which is quite reasonable.我标记接受@Alexey Romanov 的建议,这是非常合理的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM