Best way to handle such scenario where “smart cast

2019-01-28 00:57发布

问题:

I wonder what is the best way to handle such scenario

class Person(var name:String? = null, var age:Int? = null){
    fun test(){
        if(name != null && age != null)
            doSth(name, age) //smart cast imposible
    }

    fun doSth (someValue:String, someValue2:Int){

    }
}

What is the simplest way to call doSth method and making sure that name and age are nt null?

I am looking for something simple as with one variable scenario where I would simply use let

name?.let{ doSth(it) } 

回答1:

You can nest let as much as you like so:

fun test(){
    name?.let { name ->
        age?.let { age ->
            doSth(name, age) //smart cast imposible    
        }
    }
}

Another approach, that might be easier to follow, is to use local variables:

fun test(){
    val name = name
    val age = age
    if(name != null && age != null){
        doSth(name, age)
    }
}

Last but not least, consider changing Person to be immutable like so:

data class Person(val name:String? = null, val age:Int? = null){
    fun test(){
        if(name != null && age != null){
            doSth(name, age)
        }
    }
    ...
}


回答2:

For the cast to be possible you have to make a local copy of the value somehow. In Kotlin this is best done explicitly:

val name = name
val age = age
if(name != null && age != null){
    doSth(name, age)
}

The let function hides this behind an abstraction layer, which is not the best IMHO.



回答3:

There's a nice, little lib that allows for writing let-like code with multiple variables. It's open-source and you can find it on GitHub, it's called Unwrap

Example based on readme:

unwrap(_a, _b, _c) { a, b, c ->
    println("$a, $b$c") // all variables are not-null
}

All unwrap(...) methods are marked inline so there should be no overhead with using them.

By the way, this lib also allows to handle situation when there are some null variables (the nah() method).



回答4:

If you want to take it a little "extreme" you could define an extension function on Pair<String?,Int?> that hides the logic for you:

fun Pair<String?,Int?>.test(block: (String, Int) -> Unit) {
    if(first != null && second != null) {
         block(first, second)
    }
}

then, calling it will be a little more concise

(name to age).test { n, a ->
   println("name: $n age: $a")
}

However, it won't really help you (since you could as well define this as a function inside the Person class itself), unless you need this kind of functionality really often throughout the whole project. Like I said, it seems overkill.

edit you could actually make it (a little) more useful, by going fully generic:

fun <T,R> Pair<T?,R?>.ifBothNotNull(block: (T, R) -> Unit) {
    if(first != null && second != null){
         block(first, second)
    }
}


回答5:

In addition to miensol's answer there are various ways to copy property values into function variables to enable smart cast. e.g.:

  1. Intermediary function:

    class Person(var name: String? = null, var age: Int? = null) {
        fun test() = test(name, age)
        private fun test(name: String?, age: Int?) {
            if (name != null && age != null)
                doSth(name, age) //smart cast possible
        }
    
        fun doSth(someValue: String, someValue2: Int) {
    
        }
    }
    
  2. Anonymous function:

    class Person(var name: String? = null, var age: Int? = null) {
        fun test() = (fun(name: String?, age: Int?) {
            if (name != null && age != null)
                doSth(name, age) //smart cast possible
        })(name, age)
    
        fun doSth(someValue: String, someValue2: Int) {
    
        }
    }
    
  3. Default arguments:

    class Person(var name: String? = null, var age: Int? = null) {
        fun test(name: String? = this.name, age: Int? = this.age) {
            if (name != null && age != null)
                doSth(name, age) //smart cast possible
        }
    
        fun doSth(someValue: String, someValue2: Int) {
    
        }
    }
    


回答6:

It is possible to define an inline method that allows you to take N parameters in order to avoid nesting lets (I'm basing my answer on this).

inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}

Then

fun test() {
    safeLet(name, age, {name, age -> 
        doSth(name, age) //smart cast
    });
}


回答7:

I was having the problem while assigning text to textview with same problem description. All I did was putting double exclamation mark after the name of my textview. For example:

var name:TextView?=null
name = findViewById(R.id.id_name)
name!!.text = "Your text"


标签: kotlin