Kotlin项目中使用的遗留Java库中的空安全

时间:2017-06-14 17:30:14

标签: 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;
    }
}

使用@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}")

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

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

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

println("Value4: ${bar.unannotatedString.length}") // throws NPE

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

3 个答案:

答案 0 :(得分:7)

Kotlin视图中该变量的类型为String!,即platform type

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

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

作为一个额外的例子,如果您从Java重写方法,那么参数将再次成为平台类型,无论您是否将它们标记为可空,它都取决于您。如果你有这个Java接口:

interface Foo {
    void bar(Bar bar);
}

然后这些都是Kotlin的有效实现:

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

class B : Foo {
    fun bar(bar: Bar) { ... }
}

答案 1 :(得分:5)

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

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?"

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

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

  

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

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

答案 2 :(得分:0)

Java代码可以使用注释传递有关可空性的信息:

  • @Nullable字符串->被视为字符串?在科特林

  • @NotNull字符串->在科特林中被视为字符串

不存在注释时,Java类型在Kotlin中成为平台类型。平台类型是Kotlin没有可空性信息的类型-您可以将其视为可空或非空类型。这意味着您对使用此类型执行的操作承担全部责任(就像在Java中一样)。编译器将允许所有操作。与Java中一样,如果对null值执行非null安全的操作,则会获得NPE。

如果Kotlin将Java的所有传入值都视为可空值,我们可以避免进行空值检查,但是最终将产生大量冗余的空值检查,这些值永远不能为空值,因此Kotlin设计人员提出了平台类型。

请注意,您不能在Kotlin中声明平台类型,它们只能来自Java。 String!符号是Kotlin编译器表示平台类型的方式,它强调该类型的可空性是未知的。您不能在Kotlin代码中使用此语法。

根据要如何处理可能的空值,您可以使用以下运算符:

  • 字符串->调用带有参数的非空安全操作 可能为null不允许,将由编译器

  • 进行标记
  • 字符串?->您可以对其执行的一组操作受到限制 通过编译器,如果您不想传递可为空的值,则表示 强制处理它(比较它将为空值->编译器将 记住这一点,并在范围内将值视为非空值

  • 字符串?。->(安全呼叫运营商)(如果您尝试使用的值) 调用该方法不为空,则该方法正常执行,否则 呼叫被跳过,并且返回null

  • 字符串?->( Elvis运算符,也是非Coalescing运算符) 运算符取两个值,如果是,则结果为第一个值 不为空或第二个(如果第一个为空)

  • 字符串!->(非空断言)-对于null值,将引发异常

  • ?. let ->(让功能与安全呼叫操作员一起)-让 函数仅针对非null值被调用,否则没有任何反应