admin管理员组

文章数量:1530263

前言

Definitely non-nullable types,直译为,绝对不可能为空的类型

Kotlin 本身就有可空不可空类型,为何又来一个Definitely non-nullable types呢?

背景

一起看看 Kotlin 的官方 KEEP 了解下设计初衷。

fun <T : CharSequence?> foo(t: T, tn: T?) {
    t.length // call is not allowed, `t` may be null
    tn.length // call is not allowed, `tn` may be null
}

fun <F : CharSequence> bar(f: F, fn: F?) {
    f.length // call is allowed, `f` may not be null
    fn.length // call is not allowed, `fn` may be null
}

示例 1 中,泛型类型 T 的上界是 CharSequence?可空,所以参数 T 或者 T? 都可空;

示例 2 中,泛型类型 F 的上界是 CharSequence不可空 ,所以参数类型 T 不可空, T? 可空。

也就是说,基于泛型类型参数的类型可能可空也可能不可空,具体取决于泛型类型边界的可空性。

泛型类型参数上界TT?
可空(如 String?可空可空
不可空(如 String不可空可空

从上可以看出,T? 总是能表示可空类型,T 的可空与不可空根据泛型类型边界而定。

当泛型类型上界为 String 等非空类型时,我们可以用 T? 表示其可空类型;

当泛型类型上界为String?等可空类时,我们用什么表示其非空类型呢?

定义一个 Java 接口 JBox,泛型方法 put 参数为不可空泛型类型T

public interface JBox {
    <T> void put(@NotNull T t);
}

Kotlin中定义 JBoxImpl 实现 JBox 接口,下方是自动生成的代码。

class JBoxImpl : JBox {
    override fun <T : Any?> put(t: T) {
        TODO("Not yet implemented")
    }
}

可以看出,IDEA 自动帮我们生成的代码并不完全正确。JBox 接口内申明的 put 方法的参数通过注解标识为非空,而 IDEA 帮我们生成的实现代码却让其参数可空。虽然不影响运行,但是 IDEA 提供了修改 Tips 。

帮开发者自动补全代码,然后提示告警,IDEA 钓鱼执法?

按照提示,我们将 T:Any? 调整为 T:Any

class JBoxImpl : JBox {
    override fun <T : Any> put(t: T) {
        println(t.toString())
    }
}

但是上述代码,JBoxImpl里的 put方法只能接受非空类型的泛型参数,而 JBox 中仅仅只有 put 接口的参数非空而已. 无形中,我们缩小了接口的承载范围。

所以,Definitely non-nullable types 应运而生,使用 T & Any 表示。

class JBoxImpl : JBox {
    override fun <T> put(t: T & Any) {
    	……
    }
}
public interface JBox {
    <T> void put(@NotNull T t);
}

对齐了,舒服了。

经典示例

fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y

fun main() {
    // OK
    elvisLike<String>("", "").length
    // Error: 'null' cannot be a value of a non-null type
    elvisLike<String>("", null).length

    // OK
    elvisLike<String?>(null, "").length
    // Error: 'null' cannot be a value of a non-null type
    elvisLike<String?>(null, null).length
}

本文标签: 有趣Kotlinx0Fnullabletypes