Kotlin项目中使用的旧Java库中的空安全性

Jan*_*ski 3 java annotations kotlin kotlin-interop kotlin-null-safety

假设我在旧/遗留Java库中有特定代码:

public class JavaClass {
    private String notNullString;
    private String nullableString;
    private String unannotatedString;

    public JavaClass(@NotNull String notNullString,
                     @Nullable String nullableString,
                     String unannotatedString) {

        this.notNullString = notNullString;
        this.nullableString = nullableString;
        this.unannotatedString = unannotatedString;
    }

    @NotNull
    public String getNotNullString() {
        return notNullString;
    }

    @Nullable
    public String getNullableString() {
        return nullableString;
    }

    public String getUnannotatedString() {
        return unannotatedString;
    }
}
Run Code Online (Sandbox Code Playgroud)

前两个参数使用@NotNull和@Nullable注释正确注释(使用jetbrains.annotations).第三个(unnanotatedString)没有正确的注释.

当我在我的Kotlin代码中使用这个类并将所有构造函数参数设置为非null值时,一切都很好:

val foo = JavaClass("first string", "second string", "third string")

println("Value1: ${foo.notNullString.length}")
println("Value2: ${foo.nullableString?.length}")
println("Value3: ${foo.unannotatedString.length}")
Run Code Online (Sandbox Code Playgroud)

第一个值是非null,所以我可以在没有安全调用的情况下访问它.第二个值我需要使用安全调用(nullableString?.length),如果没有,我有一个编译时错误,到目前为止一直很好.在第三个值(unannotatedString)我可以在没有安全调用的情况下使用它,它编译得很好.

但是当我将第三个参数设置为"null"时,我没有得到编译时错误(不需要安全调用,只有运行时NullPointerException:

val bar = JavaClass("first string", "second string", null)

println("Value4: ${bar.unannotatedString.length}") // throws NPE
Run Code Online (Sandbox Code Playgroud)

这是预期的行为吗?Kotlin的编译器是否将带注释的Java方法与使用@NotNull注释的方法相同?

zsm*_*b13 7

Kotlin视图中该变量的类型将String!平台类型.

他们最初使每个变量都来自Java可空,但是他们在语言设计过程中后来改变了这个决定,因为它需要太多的null处理并且需要太多的安全调用来扰乱代码.

相反,由您来评估来自Java的对象是否可能null,并相应地标记其类型.编译器不会对这些对象强制执行null安全性.


作为一个额外的例子,如果你从Java覆盖一个方法,那么参数将再次成为平台类型,而你是否将它们标记为可空是由你自己决定的.如果你有这个Java接口:

interface Foo {
    void bar(Bar bar);
}
Run Code Online (Sandbox Code Playgroud)

那么这些都是Kotlin中它的有效实现:

class A : Foo {
    fun bar(bar: Bar?) { ... }
}

class B : Foo {
    fun bar(bar: Bar) { ... }
}
Run Code Online (Sandbox Code Playgroud)


nha*_*man 5

每当Kotlin编译器不知道类型的可空性是什么时,类型就变成平台类型,用单个表示!:

public String foo1() { ... }
@NotNull public String foo2() { ... }
@Nullable public String foo3() { ... }

val a = foo1() // Type of a is "String!"
val b = foo2() // Type of b is "String"
val c = foo3() // Type of c is "String?"
Run Code Online (Sandbox Code Playgroud)

这意味着,"我不知道类型是什么,你可能需要检查它".

Kotlin编译器不对这些类型强制执行空值检查,因为它可能是不必要的:

Java中的任何引用都可能为null,这使得Kotlin对严格的null安全性的要求对来自Java的对象不切实际.(...)当我们在平台类型的变量上调用方法时,Kotlin在编译时不会发出可空性错误,但是调用可能在运行时失败,因为空指针异常或Kotlin生成的断言以防止空值传播:

val item = list[0] // platform type inferred (ordinary Java object)
item.substring(1) // allowed, may throw an exception if item == null
Run Code Online (Sandbox Code Playgroud)