因为jspecify,无法在Kotlin中实现ResultSetExtractor
我不能让一个类在Kotlin中实现Spring的 ResultSetExtractor,原因在于Kotlin如何在泛型中解释 T extends @Nullable。
使用Kotlin 2.3.21和 Spring 7.0.7。
import org.springframework.jdbc.core.ResultSetExtractor
import java.sql.ResultSet
class CustomRSE<T> : ResultSetExtractor<T> {
override fun extractData(rs: ResultSet): T? {
return null
}
}
错误是
返回类型 'fun extractData(rs: ResultSet): T?' 不是在 'com.infobip.mdp.CustomRSE' 中定义的被覆盖成员 'fun extractData(rs: ResultSet): T' 的返回类型的子类型。
这哪里错了?!唯一的解决办法是 -Xjspecify-annotations=warn
这里是谁的错?Kotlin还是Spring还是jspecify?Kotlin故意忽略了该注解。
https://kotlinlang.org/docs/java-interop.html#jspecify-support
该注解不适用于类型变量(泛型)。类型变量在提供具体的可空或非空类型之前,保持对空值不敏感。
作为参考,这里是Spring 7 ResultSetExtractor 接口:
@FunctionalInterface
public interface ResultSetExtractor<T extends @Nullable Object> {
/**
* @return an arbitrary result object, or {@code null} if none
* (the extractor will typically be stateful in the latter case).
*/
T extractData(ResultSet rs) throws SQLException, DataAccessException;
}
解决方案
你代码中的类型参数 T 被 CustomRSE<T> 指定为没有具体的上界。根据定义,这意味着实际的上界将是 Any?:所有类型都被允许,甚至包括可空类型。这意味着在你实际实例化对象的任何地方,你都可以在这里使用一个可空类型。比如:
val rse = CustomRSE<String?>()
CustomRSE 实现 ResultSetExtractor,并且你在该泛型接口上使用相同的类型参数。当你实现该接口时,你不能让 extractData 返回一个 T?,因为接口声明只有 T 可以被返回。一个 T 本身也可能是可空的,但如果你使用一个非空类型——比如 String——那么 extractData 就永远不能返回 String?。这就是为什么把返回类型改为 T? 不被允许,否则你以后可能会遇到空指针异常。
按照你声明的 CustomRSE,在你尝试时你不能显式返回 null。只有在运行时你实际用一个可空的类型实例化 CustomRSE,并且在 extractData 内部以某种方式拿到这样的实例时,才会返回 null。然后你可以在那儿返回它,并且在那个时刻它可能为null。
然而,如果你希望在 extractData 中能够为任意使用的类型参数 T 显式 返回 null,那么你必须明确声明:
class CustomRSE<T> : ResultSetExtractor<T?> {
override fun extractData(rs: ResultSet): T? {
return null
}
}
在这里你不仅告诉 ResultSetExtractor 允许为 CustomRSE 选择的类型 T(例如,非空的 String),而且你还额外允许 null。
现在你可以将 extractData 的返回类型声明为 T?,并在其实现体中显式返回 null。