6

假设我在旧/旧 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 代码中使用此类并将所有构造函数参数设置为非空值时,一切都很好:

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

println("Value1: ${foo.notNullString.length}")
println("Value2: ${foo.nullableString?.length}")
println("Value3: ${foo.unannotatedString.length}")

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

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

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

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

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

4

3 回答 3

9

从 Kotlin 的角度来看,该变量的类型将是String!,它是一个平台类型

他们最初使来自 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) { ... }
}
于 2017-06-14T17:38:51.400 回答
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?"

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

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

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

val item = list[0] // platform type inferred (ordinary Java object)
item.substring(1) // allowed, may throw an exception if item == null
于 2017-06-14T17:34:45.057 回答
0

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

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

  • @NotNull 字符串 -> 在 Kotlin 中被视为字符串

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

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

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

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

  • String -> 不允许使用可能为 null 的参数调用非 null 安全操作,编译器将对其进行标记

  • 细绳?-> 你可以对其执行的操作集受编译器的限制,如果你不想传递一个可为空的值,你就不得不处理它(比较它是否为空值 -> 编译器会记住这一点并处理该值在范围内为非空)

  • 细绳?。-> ( Safe-Call Operator ) 如果您尝试调用该方法的值不为 null,则该方法正常执行,否则跳过调用并返回 null

  • String?: -> ( Elvis Operator, also Non-Coalescing Operator ) 这个操作符有两个值,如果它不为空,它的结果是第一个值,如果第一个为空,它的结果是第二个

  • 细绳!!-> ( Non-Null Assertion ) - 对于空值,抛出异常

  • ?.let -> ( Let Function with Safe-Call Operator ) - Let Function只为非空值调用,否则什么也不会发生

于 2020-01-25T09:58:08.417 回答