【问题标题】:How can I tell kotlin that a function doesn't return null if the parameter is not null?如果参数不为 null,我如何告诉 kotlin 函数不返回 null?
【发布时间】:2018-09-23 19:43:06
【问题描述】:

我想编写一个方便的扩展来从 Map 中提取值,同时解析它们。如果解析失败,该函数应该返回一个默认值。这一切都很好,但我想告诉 Kotlin 编译器,当默认值不为 null 时,结果也不会为 null。我可以通过 @Contract 注释在 Java 中实现这一点,但它似乎在 Kotlin 中不起作用。这可以做到吗?合同不适用于扩展功能吗?这是 kotlin 的尝试:

import org.jetbrains.annotations.Contract

private const val TAG = "ParseExtensions"

@Contract("_, !null -> !null")
fun Map<String, String>.optLong(key: String, default: Long?): Long? {
    val value = get(key)
    value ?: return default

    return try {
        java.lang.Long.valueOf(value)
    } catch (e: NumberFormatException) {
        Log.e(TAG, e)
        Log.d(TAG, "Couldn't convert $value to long for key $key")

        default
    }
}

fun test() {
    val a = HashMap<String, String>()

    val something: Long = a.optLong("somekey", 1)
}

在上面的代码中,IDE 将突出显示分配给something 的错误,尽管调用 optLong 时使用了非空默认值 1。作为比较,这里有类似的代码,通过注释和合约测试可空性在 Java 中:

public class StackoverflowQuestion
{
    @Contract("_, !null -> !null")
    static @Nullable Long getLong(@NonNull String key, @Nullable Long def)
    {
        // Just for testing, no real code here.
        return 0L;
    }

    static void testNull(@NonNull Long value) {
    }

    static void test()
    {
        final Long something = getLong("somekey", 1L);
        testNull(something);
    }
}

上面的代码没有显示任何错误。只有当 @Contract 注释被删除时,IDE 才会警告对 testNull() 的调用可能为空值。

【问题讨论】:

  • 为什么还要有默认值?只需让方法调用者以与您相同的方式处理它。如果value 为空,则返回空。 map.optLong("key") ?: 6L
  • 另外,考虑使用string.toLongOrNull() 而不是Long.valueOf(string) 它会自动处理错误格式。
  • @killjoy 使用 null 作为默认值可能意味着几件事,而不仅仅是找不到值。如果他认为使用空默认值对这个用例有好处,那么可以添加使default 参数默认为null

标签: android kotlin nullable static-analysis


【解决方案1】:

你可以通过使函数泛型来做到这一点。

fun <T: Long?> Map<String, String>.optLong(key: String, default: T): T 
{
    // do something.
    return default
}

可以这样使用:

fun main(args: Array<String>) {
    val nullable: Long? = 0L
    val notNullable: Long = 0L

    someMap.optLong(nullable) // Returns type `Long?`
    someMap.optLong(notNullable) // Returns type `Long`
}

这是因为Long?Long 的超类型。通常会推断类型,以便根据参数返回可空或不可空类型。

这将“告诉 Kotlin 编译器,当默认值不为 null 时,结果也不会为 null。”

【讨论】:

  • return 分支迫使我将Long! 转换为T 的未经检查的转换,但由于这是在辅助函数内部,我不介意。
  • @GrzegorzAdamHankiewicz 您的方法签名最终得到了什么?我认为不需要将Long! 转换为T,因为它是T 的子类型(某种)
  • 我复制了您的示例并将do something 替换为上面的代码。
  • 这适用于 Kotlin 调用者,其中返回值取决于传递参数的 已声明 类型。但是@Contract 还考虑了其​​他因素,例如您是否已经检查过Long? 是否实际上为空或者可以转换为Long!。有关如何使 @Contract 与 Kotlin 扩展函数一起使用的说明,请参阅 my answer(它也允许它在 Java 调用者中工作)
【解决方案2】:

很遗憾你不能这样做,在 Kotlin 1.2 或更低版本中。

然而,Kotlin 正在开发 contract dsl,它尚未宣布,它在 ATM 上不可用(因为它们在 stdlib 中被声明为 internal)但你可以使用一些技巧在你的代码中使用它们(通过编译你自己一个标准库,把它们都公开)。

您可以在 stdlib ATM 中看到它们:

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

也许会有类似的东西

contract {
   when(null != default) implies (returnValue != null)
}

将来可以解决您的问题。

解决方法

我个人建议您将 default 的类型替换为 NotNull Long 并将其称为

val nullableLong = blabla
val result = nullableLong?.let { oraora.optLong(mudamuda, it) }

resultLong?,只有当nullableLong 为空时才为空。

【讨论】:

  • 太棒了,我不知道他们正在做的事情。
【解决方案3】:

@Contract 确实可以与 Kotlin 扩展函数一起使用,只需对其进行更改以与编译后的字节码一起使用。扩展函数在字节码中编译为静态方法:

fun ClassA?.someMethod(arg: ClassB): ClassC? {
    return this?.let { arg.someMethod(it)!! }
}

Java 会将此视为可为空的,因此它会要求您对结果进行空检查。但真正的约定是:“如果 ClassA 为 null,则返回 null;否则,如果 ClassA 不为 null,则返回非 null”。但 IntelliJ 不理解这一点(至少来自 Java 源)。

当该方法被编译为 Java 字节码时,它实际上是:

@Nullable static ClassC someMethod(@Nullable ClassA argA, @NonNull ClassB argB) {}

因此,在编写 @Contract 时,您需要考虑合成的第一个参数:

@Contract("null, _ -> null; !null, _ -> !null")
fun ClassA?.someMethod(arg: ClassB): ClassC? {...}

之后,IntelliJ 会理解静态方法的约定,并且会理解返回值的可空性取决于第一个参数的可空性。

因此,与此问题有关的简短版本是,您只需在合同中添加一个额外的 _ 参数,以表示“this”参数:

@Contract("_, _, !null -> !null") // args are: ($this: Map, key: String, default: Long?)
fun Map<String, String>.optLong(key: String, default: Long?): Long? {

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-30
    • 2022-01-03
    • 1970-01-01
    • 2018-08-21
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    相关资源
    最近更新 更多