问题描述
我最近发现一个 Kotlin/Spring Boot 显示了我不完全理解的行为。
让我们从一个非常教科书般的纯 Kotlin 示例开始:
val map: Map<String,Any> = mapOf(
"name": "John","age": 21,"job": null
)
很容易理解,上面的代码片段会导致编译器错误,提醒我们由于那个 ("job",null) 对,我们实际上将 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
这是故意的吗?它背后的逻辑是什么?
解决方法
这是一个 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 代码互操作时,编译器无法始终检查您是否正确!