如果参数不为null,如何告诉kotlin函数不返回null?

时间:2018-04-13 12:34:43

标签: android kotlin nullable static-analysis

我想编写一个便利扩展来从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赋值中的错误,尽管使用非空默认值1调用optLong。为了进行比较,这里是测试可空性的类似代码通过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()的调用可能为空值。

3 个答案:

答案 0 :(得分:1)

很可惜你不能在Kotlin 1.2或更低版本中这样做。

然而,Kotlin正在研究contract dsl尚未通知的,这是不可用的ATM(因为它们在stdlib中被声明为internal)但你可以使用一些黑客在你的代码(通过自己编译stdlib,使所有这些都公开)。

您可以在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)
}

将来可以解决您的问题。

解决方法

就我个人而言,我建议您使用NotNull default替换Long的类型并将其称为

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

resultLong?,仅当nullableLong为空时才为空。

答案 1 :(得分: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时,结果也不会为空。&#34;

答案 2 :(得分:0)

fun ClassA?.someMethod(arg: ClassB): ClassC? { return this?.let { arg.someMethod(it)!! } } 确实适用于Kotlin扩展功能,只需更改它即可使用编译后的字节码。扩展功能以字节码形式编译为静态方法:

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

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

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

@Contract

因此,在编写@Contract("null, _ -> null; !null, _ -> !null") fun ClassA?.someMethod(arg: ClassB): ClassC? {...} 时需要考虑综合的第一个参数:

_

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

因此,与此问题相关的简称是,您只需要在合同中添加一个额外的@Contract("_, _, !null -> !null") // args are: ($this: Map, key: String, default: Long?) fun Map<String, String>.optLong(key: String, default: Long?): Long? { 参数即可代表“ this”参数:

{{1}}