How do we implement a Java interface like this in Kotlin with a nullable (!) type for T
?
interface Foo<T> {
Class<T> typeWitness();
}
Real use cases include jOOQ’s Converter
, and I guess every library that relies on Java reflection will have similar cases.
Now, in Kotlin we observe:
import kotlin.reflect.full.*
import kotlin.reflect.jvm.javaType
class Bar: Foo<String?> {
override fun typeWitness(): Class<String?> {
var result: Class<String?>
// Okay but unchecked cast:
result = String::class.java as Class<String?>
// Nope, none of those compile:
result = String?::javaClass
result = String?::class.java
result = String::class.java
// A more complicated route, but _still_ requires the unsafe cast
result = String::class.createType(nullable = true).javaType
as Class<String?>
// (!) BUT:
// An operation is not implemented:
// Java type is not yet supported for types created
// with createType
return result
}
}
So the only variant that works uses an unsafe cast (which we know to not cause a runtime error ever, but still). Of course, we can hide the unpleasantness away:
@Suppress("UNCHECKED_CAST")
fun <T> Class<T>.nullable() : Class<T?> {
return this as Class<T?>
}
But that still leaves a taste of defeat. Should there not be a statically type safe way to construct a Class
instances for nullable types? String?::class.java
strikes me as obvious candidate.
Side Note: If we implement the interface in Java with all the annotations:
public class JBar implements Foo<@Nullable String> {
@Override @NotNull
public Class<@Nullable String> typeWitness() {
return String.class;
}
}
We might expect that, after mapping, JBar : Foo<String?>
. But:
val f: Foo<String> = JBar()
compiles. Bug or Feature?