我有一个功能:

object CliUtils {
    var skipOptionalArgs = false

    fun readOptional(variableName: String, defaultValue: String? = null): String? {
        if (skipOptionalArgs) return defaultValue
        print("Provide the $variableName (optional${if (defaultValue != null) ", default: $defaultValue" else ""}): ")
        return readln().takeIf { it.isNotEmpty() } ?: defaultValue
    }
}

当我像这样使用这个函数时...

val inputName = readOptional("input name", "Input")

...inputName的类型是String?.根据我给出的"Input"defaultValue,我知道变量不是null.

我可以使用双感叹号,如下所示:

val inputName = readOptional("input name", "Input")!!

但我觉得应该有一种方法来定义readOptional函数,这样就可以推断出字符串的返回类型,而不使用双感叹号.我想到了合同,但对我的知识来说,合同只能引用参数,不能引用返回类型.我还想到了将defaultValue参数类型的类型链接到返回类型的泛型,但我无法让它与其空性一起工作.

有没有办法让Kotlin明白,如果是defaultValue != null,返回值也不是空的?

推荐答案

使用泛型是可能的:

@Suppress("UNCHECKED_CAST")
fun <T : String?> readOptional(variableName: String, defaultValue: T = null as T): T {
    if (skipOptionalArgs) return defaultValue
    print("Provide the $variableName (optional${if (defaultValue != null) ", default: $defaultValue" else ""}): ")
    return (readln().takeIf { it.isNotEmpty() }  ?: defaultValue) as T
}

呼叫方不再需要邪恶的!!支票:

val inputName: String = readOptional("input name", "Input")

val inputNameNullable: String? = readOptional("input name", null)

val inputNameDefaultNullable: String? = readOptional("input name")

但我们要付出的代价是unchecked cast美元.这是因为编译器无法确保该方法在每个条件下都返回非空默认参数.

因此,我们让调用站点可以轻松地使用所有变体,而无需额外的判断.但正如前面提到的,编译器不能阻止我们错误地使用它.仍然可以额外设置(错误的)泛型类型,或者让它从val类型推断错误:

val inputNameDefaultTypedWrong: String = readOptional<String>("input name")
val inputNameDefaultNullableWrongInferred: String = readOptional("input name") 

Edit:这也可以与额外的方法重载相结合来解决,如@Tenfour04:

fun readOptional(variableName: String): String? = readOptional(variableName, null)

我的 idea 和更多解释也可以在这里找到: Kotlin generics with nullable types

Kotlin相关问答推荐

文本正在被切断在200%的屏幕比例在Jetpack Compose

等待下一个值时暂停Kotlin Coroutine

为什么可组合对象看似无状态(唯一传递的参数是函数,而不是状态),但会进行重组

Mockk:对同一函数进行两次存根会忽略第一个行为

Kotlin 基于参数类型的返回类型推断

如何获取@JsonProperty 名称列表?

在kotlin中匹配多个变量

Saripaar formvalidation 在 kotlin 中第二次不起作用

Kotlin 枚举中的循环引用

将 Firebase 数据快照反序列化为 Kotlin 数据类

如何在 Kotlin 中传递有界通配符类型参数?

如果我可以将 Flow 和 StateFlow 与生命周期范围 \ viewLifecycleOwner.lifecycleScope 一起使用,那么在 ViewModel 中使用 LiveData 有什么意义

Kotlin:使用Gradle进行增量编译

如何让数据类在Kotlin中实现接口/扩展超类属性?

我应该使用Kotlin数据类作为JPA实体吗?

Kotlin flatMap - map

Kotlin var lazy init

Recyclerview: listen to padding click events

用 kotlin 学习 Android MVVM 架构组件

Kotlin:获取文件的扩展名,例如.txt