Kotlin 项目中使用的遗留 Java 库中的空安全
Null safety in legacy Java libraries used in Kotlin projects
假设我在 old/legacy 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 代码中使用这个 class 并将所有构造函数参数设置为非空值时,一切都很好:
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 注释的方法相同?
只要 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?"
这意味着 "I don't know what the type is, you may need to check it"。
Kotlin 编译器不会对这些类型强制执行 null 检查,因为它可能是不必要的:
Any reference in Java may be null, which makes Kotlin's requirements
of strict null-safety impractical for objects coming from Java. (...)
When we call methods on variables of platform types, Kotlin does not
issue nullability errors at compile time, but the call may fail at
runtime, because of a null-pointer exception or an assertion that
Kotlin generates to prevent nulls from propagating:
val item = list[0] // platform type inferred (ordinary Java object)
item.substring(1) // allowed, may throw an exception if item == null
从 Kotlin 的角度来看,该变量的类型将是 String!
,即 platform type。
他们最初让来自 Java 的每个变量都可以为 null,但他们后来在语言设计过程中改变了这个决定,因为它需要太多 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) { ... }
}
Java 代码可以使用注释传递有关可空性的信息:
@Nullable String -> 被视为字符串?在 Kotlin 中
@NotNull 字符串 -> 在 Kotlin 中被视为字符串
当注释不存在时,Java 类型在 Kotlin 中成为 平台类型。平台类型是 Kotlin 没有可空性信息的类型 - 您可以将其视为可空或非空类型。这意味着您对使用此类型执行的操作承担全部责任(就像在 Java 中一样)。编译器将允许所有操作。就像在 Java 中一样,如果您对空值执行非空安全操作,您将获得 NPE。
如果 Kotlin 将来自 Java 的所有传入值都视为可为空,我们可以避免空检查,但我们最终会为永远不能为空的值进行大量冗余空检查,因此 Kotlin 设计师来了与平台类型有关。
请注意,您不能在 Kotlin 中声明平台类型,它们只能来自 Java。 String! 表示法是 Kotlin 编译器表示平台类型的方式,它强调类型的可空性是未知的。您不能在 Kotlin 代码中使用此语法。
根据您希望如何处理可能的空值,您可以使用以下运算符:
String -> 使用参数调用非空安全操作
可能为 null 是不允许的,将被编译器标记
String? -> 您可以对其执行的操作集受到限制
由编译器,如果你不想传递一个可为空的值,你是
被迫处理它(比较它将为空值 -> 编译器将
记住这一点,并在范围内将该值视为非空)
String?. -> (Safe-Call Operator) 如果您正在尝试的值
调用方法不为空,方法正常执行,否则
跳过调用并返回 null
String?: -> (Elvis Operator, also Non-Coalescing Operator) 这个
运算符有两个值,如果它的结果是第一个值
如果第一个为空,则不为空或第二个
String!! -> (Non-Null Assertion) - 对于 null 值抛出异常
?.let -> (Let Function with Safe-Call Operator) - 让
函数 仅针对非空值调用,否则什么也不会发生
假设我在 old/legacy 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 代码中使用这个 class 并将所有构造函数参数设置为非空值时,一切都很好:
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 注释的方法相同?
只要 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?"
这意味着 "I don't know what the type is, you may need to check it"。
Kotlin 编译器不会对这些类型强制执行 null 检查,因为它可能是不必要的:
Any reference in Java may be null, which makes Kotlin's requirements of strict null-safety impractical for objects coming from Java. (...) When we call methods on variables of platform types, Kotlin does not issue nullability errors at compile time, but the call may fail at runtime, because of a null-pointer exception or an assertion that Kotlin generates to prevent nulls from propagating:
val item = list[0] // platform type inferred (ordinary Java object) item.substring(1) // allowed, may throw an exception if item == null
从 Kotlin 的角度来看,该变量的类型将是 String!
,即 platform type。
他们最初让来自 Java 的每个变量都可以为 null,但他们后来在语言设计过程中改变了这个决定,因为它需要太多 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) { ... }
}
Java 代码可以使用注释传递有关可空性的信息:
@Nullable String -> 被视为字符串?在 Kotlin 中
@NotNull 字符串 -> 在 Kotlin 中被视为字符串
当注释不存在时,Java 类型在 Kotlin 中成为 平台类型。平台类型是 Kotlin 没有可空性信息的类型 - 您可以将其视为可空或非空类型。这意味着您对使用此类型执行的操作承担全部责任(就像在 Java 中一样)。编译器将允许所有操作。就像在 Java 中一样,如果您对空值执行非空安全操作,您将获得 NPE。
如果 Kotlin 将来自 Java 的所有传入值都视为可为空,我们可以避免空检查,但我们最终会为永远不能为空的值进行大量冗余空检查,因此 Kotlin 设计师来了与平台类型有关。
请注意,您不能在 Kotlin 中声明平台类型,它们只能来自 Java。 String! 表示法是 Kotlin 编译器表示平台类型的方式,它强调类型的可空性是未知的。您不能在 Kotlin 代码中使用此语法。
根据您希望如何处理可能的空值,您可以使用以下运算符:
String -> 使用参数调用非空安全操作 可能为 null 是不允许的,将被编译器标记
String? -> 您可以对其执行的操作集受到限制 由编译器,如果你不想传递一个可为空的值,你是 被迫处理它(比较它将为空值 -> 编译器将 记住这一点,并在范围内将该值视为非空)
String?. -> (Safe-Call Operator) 如果您正在尝试的值 调用方法不为空,方法正常执行,否则 跳过调用并返回 null
String?: -> (Elvis Operator, also Non-Coalescing Operator) 这个 运算符有两个值,如果它的结果是第一个值 如果第一个为空,则不为空或第二个
String!! -> (Non-Null Assertion) - 对于 null 值抛出异常
?.let -> (Let Function with Safe-Call Operator) - 让 函数 仅针对非空值调用,否则什么也不会发生