从 Kotlin 调用 Java 时的可空规则是什么

What are nullable rules when calling Java from Kotlin

为什么 Kotlin 在一种情况下推断从 Java 返回的类型是可为空的,而在另一种情况下它可以是可空的或不可空的?

我已经检查了 HashMap.getJsonNode.get,但我无法在 calsses 中或继承链中的任何地方识别任何类似 @NotNull 的注释。是什么让 Kotlin 以不同的方式处理这 2 个调用?

我已经阅读了文档 https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types,但它的解释是使用“平台类型”,但没有解释它们是什么,也没有解释行为上的差异。

import com.fasterxml.jackson.databind.JsonNode

private fun docType(node: JsonNode, map: java.util.HashMap<String,String>) {
    val x: JsonNode = node.get("doc_type")  // DOES compile and can throw NPE at runtime
    val y: JsonNode? = node.get("doc_type") // DOES compile and Kotlin's type system will force you to check for null
    val z: String = map.get("a")            // ERROR: Type mismatch: inferred type is String? but String was expected
}

java.util.HashMap.get implements the interface method java.util.Map.get. Kotlin maps some Java types to its own types internally. The full table of these mappings is available on the website. In our particular case, we see that java.util.Map gets mapped internally to kotlin.collections.Map, whose get 函数看起来像

abstract operator fun get(key: K): V?

所以对于Kotlin而言,java.util.Map只是kotlin.collections.Map的一个有趣的名字,而java.util.Map上的所有方法实际上都有对应的签名kotlin.collections.Map(除了正确的空注释外基本相同)。

因此,虽然前两个 node.get 调用是 Java 调用和 return 平台类型,但第三个(就 Kotlin 而言)实际上是调用 Kotlin 理解的方法: 即 get 来自 它自己的 Map 类型。并且该类型已经有一个明确的可空性注释可用,因此 Kotlin 可以自信地说该值 可以 null 并且需要检查。

Kotlin 提供与 Java 的无缝互操作性,而不会影响其自身 null-safety... 几乎。一个例外是 Kotlin 假设 all 在 Java 中定义的类型是 not-null.

要理解,我们来看JsonNode.get()

平台类型

  public JsonNode get(String fieldName) { return null; }

请注意 JsonNode 是在 Java 中定义的,因此是 'platform type' - 并且 Kotlin 不会 'translate' 到 JsonNode?,即使这在技术上是正确的(因为在 Java 中所有类型都可以为空)。

从 Kotlin 调用 Java 时,for convenience 假定平台类型为 non-nullable。如果不是这种情况,您将始终 必须检查任何平台类型的任何实例是否不为空。

因此,要回答您关于 'platform type' 是什么的问题,这个术语表示

  • 一些在外部目标语言中定义的类型,
  • 你不能在 Kotlin 代码中明确提到它(但可能有一个同义的 Kotlin 等价物),
  • 为了方便起见,我们假设它是 non-nullable。
  • 符号也是 <type>!,例如 String! - 我们可以理解为 String or String?

可为空性注释

与 Kotlin 的可空 ? 符号最接近的 Java 等价物是 nullability annotations,Kotlin 编译器可以解析并考虑它。但是,none 用于 JsonNode 方法。因此 Kotlin 会很高兴地假设 node.get("") 会 return JsonNode,而不是 JsonNode?.

如您所述,为 HashMap.get(...) 定义了 none。

那么 Kotlin 如何知道 map.get("a") return 是一个可空类型?

类型推断

Type inference没办法。 (Java) 方法签名

public V get(Object key) {
  //...
}

表示HashMap<String, String>应该returnString,而不是String?。一定是有其他事情发生了...

映射类型

对于大多数 Java 类型,Kotlin 将只使用提供的定义。但对于一些,Kotlin 决定特殊对待它们,并用自己的版本完全替换 Java 定义。

可以看到the list of mapped types in the docs。虽然 HashMap 不在那里,但 Map 是。因此,当我们编写 Kotlin 代码时,HashMap 不会继承自 java.util.Map - 因为它映射到 kotlin.collections.Map

Aside: in fact if you try and use java.util.Map you'll get a warning

因此,如果我们查看 kotlin.collections.Map 定义的 get 函数的代码,we can see that it returns a nullable value type

    /**
     * Returns the value corresponding to the given [key], or `null` if such a key is not present in the map.
     */
    public operator fun get(key: K): V?

因此 Kotlin 编译器可以查看 HashMap.get(...) 并推断出,因为它正在实现 kotlin.collections.Map.get(...),returned 值 必须 是一个可以为 null 的值,在我们的例子中是 String?.

解决方法:外部注释

无论出于何种原因,Jackson 都没有使用可以解决此问题的可空性注释。幸运的是,IntelliJ 提供了一种解决方法,虽然不那么严格,但会提供有用的警告:external annotations.

一旦我按照说明操作...

  1. Alt+Enter → 'Annotate method...'

  2. Select'Nullable'注解

  3. 保存annotations.xml

现在 node.get("") 将显示警告。

此注释对 Kotlin 编译器不可见,因此它只能是警告 - 而不是编译错误。