Why is KClass not a KType?

Kotlin reflections lib (on JVM) uses KType for KParameter.type, and KFunction.returnType. I have serialization registered based on KClass in map. However, I am not able to lookup the serialization handler, for return types or parameters because the type is KType which is not the super type of KClass. I see KType.jvmEreasure returns KClass. What is best approach.

Thanks

3 Likes

KType is more than just a class. In Kotlin, a type can be nullable or not, it can have generic type arguments, and it can be annotated with type annotations. Additionally, a type’s classifier (what the type is “based” on) can be not only a particular class, but a type parameter of some declaration. For example, here:

fun <K : Any, V : Any> get(key: K): V?

K is a type, and V? is a type, and their classifiers are the corresponding type parameters, but the first type is marked as non-null and the second type is nullable.

Generally, KType is something you discover in a function signature and that denotes the set of values that function can take or return. KClass is a concrete class, with a declaration and source somewhere and a fixed fully qualified name, and you can get the (single) class of every instance at runtime.

For your problem, it might be helpful to think what value do the type’s constituents have: should the behavior differ if the type in the function signature is nullable or not? Should generic type arguments be taken into account? Do you have to handle the type annotations in any way? If all answers are no and you also don’t care about types whose classifiers are type parameters, not classes (example above), then you can safely call type.jvmErasure, or type.classifier as KClass<*> to get the KClass instance.

7 Likes

I have the same problem as Matt have. I need to map some Kotlin types (Boolean, Double, Float, Int, Long and String) to corresponding adapters that will further serialize/deserialize data into/from some representation. I see two different ways to achieve that:

  1. Map KType to Adapter as the following:

     val map = mapOf<KType, Adapter>(
             Boolean::class.createType() to BooleanAdapter(),
             Boolean::class.createType(nullable = true) to BooleanAdapter(),
             Double::class.createType() to DoubleAdapter(),
             Double::class.createType(nullable = true) to DoubleAdapter(),
             Float::class.createType() to FloatAdapter(),
             Float::class.createType(nullable = true) to FloatAdapter(),
             Int::class.createType() to IntAdapter(),
             Int::class.createType(nullable = true) to IntAdapter(),
             Long::class.createType() to LongAdapter(),
             Long::class.createType(nullable = true) to LongAdapter(),
             String::class.createType() to StringAdapter(),
             String::class.createType(nullable = true) to StringAdapter()
     )
     foo::class.declaredMemberProperties.forEach { p -> 
             map[p.returnType]?.doWork() 
     }
    
  2. Map KClassifier to Adapter as the followong:

     val map = mapOf<KClassifier, Adapter>(
             Boolean::class to BooleanAdapter(),
             Double::class to DoubleAdapter(),
             Float::class to FloatAdapter(),
             Int::class to IntAdapter(),
             Long::class to LongAdapter(),
             String::class to StringAdapter()
     )
     foo::class.declaredMemberProperties.forEach { p -> 
             map[p.returnType.classifier]?.doWork() 
     }
    

Is there any significant or important difference between these approaches except that the first one is slightly longer?

I dont think there is any other difference. :thinking:

The main difference is that the first contains nullability information, and the second doesn’t.
Also starting from Kotlin 1.3.40 you can use the typeOf function.