Kotlin Map 类型和 JdbcTemplate.queryForMap()

问题描述

我最近发现一个 Kotlin/Spring Boot 显示了我不完全理解的行为。

让我们从一个非常教科书般的纯 Kotlin 示例开始:

val map: Map<String,Any> = mapOf(
   "name": "John","age": 21,"job": null
)

很容易理解,上面的代码片段会导致编译器错误,提醒我们由于那个 ("job",null) 对,我们实际上将 Map Map 类型的变量,这是不允许的。

现在假设我们在 Spring Boot 中使用 Kotlin。我们还假设我们有一个名为 PERSON 的数据库(例如 MysqL)表,其中包含三列:

NAME of type varchar (not nullable)
AGE of type int (not nullable)
JOB of type varchar (nullable)

假设我们的表中有一行,其数据与我们之前提到的示例数据相匹配:

'John',21,NULL

使用 JdbcTemplate 实例,我们可以执行以下操作:

val map: Map<String,Any> = jdbcTemplate.queryForMap("SELECT * FROM PERSON WHERE NAME=John")
println(map)

对我来说,令人惊讶的结果是最后一段代码没有出现错误并打印出来

{NAME=John,AGE=21,JOB=null}

所以看起来我们成功地在 Map 中包含了一个 Pair

这是故意的吗?它背后的逻辑是什么?

解决方法

这是一个 Java 互操作性问题。

JVM 本身没有不可为空的类型。 Kotlin 编译器通过在编译时跟踪类型来实现这些。

这在纯 Kotlin 代码中非常有效,但在与用 Java 或其他 JVM 语言编写的代码互操作时会出现极端情况。 Kotlin 编译器有一些变通方法:它使用并识别 standard annotations 来指示可空性;一些 Java 代码使用这些(IDE 可以使用它们来显示警告)。当可以从非 Kotlin 代码传递空值时,它会插入检查。它使用 platform types 来指示何时无法确定来自非 Kotlin 代码的值的可空性。但是,它无法应对所有情况。

特别是,由于 type erasure,泛型已经有各种尴尬的极端情况。这就是我们在这里所拥有的:org.springframework.jdbc.core.JdbcTemplate 类是用 Java 编写的,因此它也对不可为空类型一无所知。问题在于类型参数,这些参数被擦除,因此编译后的代码只知道原始 Map。所以 Kotlin 编译器无法判断返回对象的类型参数是否应该为 null。

这是复杂的,因为您在代码中指定了返回类型。如果让编译器推断:

val map = jdbcTemplate.queryForMap("SELECT * FROM PERSON WHERE NAME='John'")

...然后它会将类型参数推断为 <String!,Any!>! 表示平台类型,它无法判断可空性。但是因为您已经指定了它们,编译器假定您知道自己在做什么,并且不会给出任何进一步的警告或检查。

我想这样做的寓意是不要指定返回类型,除非您确定它是正确的,因为在与非 Kotlin 代码互操作时,编译器无法始终检查您是否正确!