如果参数不为 null,我如何告诉 kotlin 函数不 return null?

How can I tell kotlin that a function doesn't return null if the parameter is not null?

我想编写一个方便的扩展来从 Map 中提取值,同时解析它们。如果解析失败,函数应该 return 一个默认值。这一切都很好,但我想告诉 Kotlin 编译器,当默认值不为 null 时,结果也不会为 null。我可以通过 @Contract 注释在 Java 中做到这一点,但它似乎在 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 WhosebugQuestion
{
    @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() 可能有一个空值。

很遗憾,在 Kotlin 1.2 或以下版本中,您不能这样做。

但是,Kotlin 正在开发尚未公布的 contract dsl,它在 ATM 中不可用(因为它们在 stdlib 中被声明为 internal),但您可以使用一些 hack 来使用它们在你的代码中(通过自己编译一个标准库,使它们全部 public)。

您可以在 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为空时才为空。

您可以通过使函数通用来实现此目的。

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 的超类型。通常会推断类型,以便 return 基于参数的可空或不可空类型。

这将"tell the Kotlin compiler that when the default value is not null, the result won't be null either."

@Contract 确实可以与 Kotlin 扩展函数一起使用,只需要将其更改为与编译后的字节码一起使用。一个扩展函数被编译成字节码作为静态方法:

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

Java 会将此视为可为空,因此它会要求您对结果进行空检查。但真正的契约是:"if ClassA is null, returns null; otherwise if ClassA is not null, returns non-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 将理解静态方法的契约,并理解 return 值的可空性取决于第一个参数的可空性。

所以简短的版本,因为它属于这个问题,你只需要在合同中添加一个额外的 _ 参数,来表示 "this" 参数:

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