
class keyword. The result is KClass<T>, where T is the type representing a class.import kotlin.reflect.KClass class A fun main() { val class1: KClass<A> = A::class println(class1) // class A val a: A = A() val class2: KClass<out A> = a::class println(class2) // class A }
A might contain an object of type A or any of its subtypes.import kotlin.reflect.KClass open class A class B : A() fun main() { val a: A = B() val clazz: KClass<out A> = a::class println(clazz) // class B }
Sinceclassis a reserved keyword in Kotlin and cannot be used as the variable name, it is a popular practice to use "clazz" instead.
- Simple name is just the name used after the
classkeyword. We can read it using thesimpleNameproperty. - The fully qualified name is the name that includes the package and the enclosing classes. We can read it using the
qualifiedNameproperty.
package a.b.c class D { class E } fun main() { val clazz = D.E::class println(clazz.simpleName) // E println(clazz.qualifiedName) // a.b.c.D.E }
simpleName and qualifiedName return null when we reference an object expression or any other nameless object.fun main() { val o = object {} val clazz = o::class println(clazz.simpleName) // null println(clazz.qualifiedName) // null }
KClass has only a few properties that let us check some class-specific characteristics:isFinal: Boolean-trueif this class isfinal.isOpen: Boolean-trueif this class has theopenmodifier. Abstract and sealed classes, even though they are generally considered abstract, will returnfalse.isAbstract: Boolean-trueif this class has theabstractmodifier. Sealed classes, even though they are generally considered abstract, will returnfalse.isSealed: Boolean-trueif this class has thesealedmodifier.isData: Boolean-trueif this class has thedatamodifier.isInner: Boolean-trueif this class has theinnermodifier.isCompanion: Boolean-trueif this class is a companion object.isFun: Boolean-trueif this class is a Kotlin functional interface and so has thefunmodifier.isValue: Boolean-trueif this class is a value class and so has thevaluemodifier.
visibility property.sealed class UserMessages private data class UserId(val id: String) { companion object { val ZERO = UserId("") } } internal fun interface Filter<T> { fun check(value: T): Boolean } fun main() { println(UserMessages::class.visibility) // PUBLIC println(UserMessages::class.isSealed) // true println(UserMessages::class.isOpen) // false println(UserMessages::class.isFinal) // false println(UserMessages::class.isAbstract) // false println(UserId::class.visibility) // PRIVATE println(UserId::class.isData) // true println(UserId::class.isFinal) // true println(UserId.Companion::class.isCompanion) // true println(UserId.Companion::class.isInner) // false println(Filter::class.visibility) // INTERNAL println(Filter::class.isFun) // true }
members: Collection<KCallable<*>>- returns all class members, including those declared by parents of this class.functions: Collection<KFunction<*>>- returns all class member functions, including those declared by parents of this class.memberProperties: Collection<KProperty1<*>>- returns all class member properties, including those declared by parents of this class.declaredMembers: Collection<KCallable<*>>- returns members declared by this class.declaredFunctions: Collection<KFunction<*>>- returns functions declared by this class.declaredMemberProperties: Collection<KProperty1<*>>- returns member properties declared by this class.
import kotlin.reflect.full.* open class Parent { val a = 12 fun b() {} } class Child : Parent() { val c = 12 fun d() {} } fun Child.e() {} fun main() { println(Child::class.members.map { it.name }) // [c, d, a, b, equals, hashCode, toString] println(Child::class.functions.map { it.name }) // [d, b, equals, hashCode, toString] println(Child::class.memberProperties.map { it.name }) // [c, a] println(Child::class.declaredMembers.map { it.name }) // [c, d] println(Child::class.declaredFunctions.map { it.name }) // [d] println(Child::class.declaredMemberProperties.map { it.name }) // [c] }
constructors property.package playground import kotlin.reflect.KFunction class User(val name: String) { constructor(user: User) : this(user.name) constructor(json: UserJson) : this(json.name) } class UserJson(val name: String) fun main() { val constructors: Collection<KFunction<User>> = User::class.constructors println(constructors.size) // 3 constructors.forEach(::println) // fun <init>(playground.User): playground.User // fun <init>(playground.UserJson): playground.User // fun <init>(kotlin.String): playground.User }
superclasses property, which returns List<KClass<*>>. In reflection API nomenclature, remember that interfaces are also considered classes, so their references are of type KClass and they are returned by the superclasses property. We can also get the types of the same direct superclass and directly implemented interfaces using the supertypes property, which returns List<KType>. This property actually returns a list of superclasses, not supertypes, as it doesn’t include nullable types, but it includes Any if there is no other direct superclass.import kotlin.reflect.KClass import kotlin.reflect.full.superclasses interface I1 interface I2 open class A : I1 class B : A(), I2 fun main() { val a = A::class val b = B::class println(a.superclasses) // [class I1, class kotlin.Any] println(b.superclasses) // [class A, class I2] println(a.supertypes) // [I1, kotlin.Any] println(b.supertypes) // [A, I2] }
interface I1 interface I2 open class A : I1 class B : A(), I2 fun main() { val a = A() val b = B() println(A::class.isInstance(a)) // true println(B::class.isInstance(a)) // false println(I1::class.isInstance(a)) // true println(I2::class.isInstance(a)) // false println(A::class.isInstance(b)) // true println(B::class.isInstance(b)) // true println(I1::class.isInstance(b)) // true println(I2::class.isInstance(b)) // true }
KTypeParameter type. We can get a list of all type parameters defined by a class using the typeParameters property.fun main() { println(List::class.typeParameters) // [out E] println(Map::class.typeParameters) // [K, out V] }
nestedClasses property.class A { class B inner class C } fun main() { println(A::class.nestedClasses) // [class A$B, class A$C] }
sealedSubclasses: List<KClass<out T>>.sealed class LinkedList<out T> class Node<out T>( val head: T, val next: LinkedList<T> ) : LinkedList<T>() object Empty : LinkedList<Nothing>() fun main() { println(LinkedList::class.sealedSubclasses) // [class Node, class Empty] }
objectInstance property of type T?, where T is the KClass type parameter. This property returns null when a class does not represent an object declaration.import kotlin.reflect.KClass sealed class LinkedList<out T> data class Node<out T>( val head: T, val next: LinkedList<T> ) : LinkedList<T>() data object Empty : LinkedList<Nothing>() fun main() { println(Node::class.objectInstance) // null println(Empty::class.objectInstance) // Empty }
toJson function which will serialize objects into JSON format.class Creature( val name: String, val attack: Int, val defence: Int, ) fun main() { val creature = Creature( name = "Cockatrice", attack = 2, defence = 4 ) println(creature.toJson()) // {"attack": 2, "defence": 4, "name": "Cockatrice"} }
toJson, I will define a couple of helper functions, starting with objectToJson, which is responsible for serializing objects to JSON and assumes that its argument is an object. Objects in JSON format are surrounded by curly braces containing property-value pairs separated with commas. In each pair, first there is a property name in quotes, then a colon, and then a serialized value. To implement objectToJson, we first need to have a list of object properties. For that, we will reference this object, and then we can either use memberProperties (including all properties in this object, including those inherited from the parent) or declaredMemberProperties (including properties declared by the class constructing this object). Once we have a list of properties, we can use joinToString to create a string with property-value pairs. We specify prefix and postfix parameters to surround the result string with curly brackets. We also define transform tospecify how property-value pairs should be transformed to a string. Inside them, we take property names using the
name property; we get property value by calling the call method from this property reference, and we then transform the result value to a string using the valueToJson function.fun Any.toJson(): String = objectToJson(this) private fun objectToJson(any: Any) = any::class .memberProperties .joinToString( prefix = "{", postfix = "}", transform = { prop -> "\"${prop.name}\": ${valueToJson(prop.call(any))}" } )
valueToJson function to serialize JSON values. JSON format supports a number of values, but most of them can just be serialized using the Kotlin string template. This includes the null value, all numbers, and enums. An important exception is strings, which need to be additionally wrapped with quotes[^9_2]. All non-basic types will be treated as objects and serialized with the objectToJson function.private fun valueToJson(value: Any?): String = when (value) { null, is Number -> "$value" is String, is Enum<*> -> "\"$value\"" // ... else -> objectToJson(value) }
import kotlin.reflect.full.memberProperties // Serialization function definition fun Any.toJson(): String = objectToJson(this) private fun objectToJson(any: Any) = any::class .memberProperties .joinToString( prefix = "{", postfix = "}", transform = { prop -> "\"${prop.name}\": ${valueToJson(prop.call(any))}" } ) private fun valueToJson(value: Any?): String = when (value) { null, is Number, is Boolean -> "$value" is String, is Enum<*> -> "\"$value\"" is Iterable<*> -> iterableToJson(value) is Map<*, *> -> mapToJson(value) else -> objectToJson(value) } private fun iterableToJson(any: Iterable<*>): String = any .joinToString( prefix = "[", postfix = "]", transform = ::valueToJson ) private fun mapToJson(any: Map<*, *>) = any.toList() .joinToString( prefix = "{", postfix = "}", transform = { "\"${it.first}\": ${valueToJson(it.second)}" } ) // Example use class Creature( val name: String, val attack: Int, val defence: Int, val traits: List<Trait>, val cost: Map<Element, Int> ) enum class Element { FOREST, ANY, } enum class Trait { FLYING } fun main() { val creature = Creature( name = "Cockatrice", attack = 2, defence = 4, traits = listOf(Trait.FLYING), cost = mapOf( Element.ANY to 3, Element.FOREST to 2 ) ) println(creature.toJson()) // {"attack": 2, "cost": {"ANY": 3, "FOREST": 2}, // "defence": 4, "name": "Cockatrice", // "traits": ["FLYING"]} }
JsonName annotation, which should set a different name for the serialized form, and JsonIgnore, which should make the serializer ignore the annotated property.// Annotations @Target(AnnotationTarget.PROPERTY) annotation class JsonName(val name: String) @Target(AnnotationTarget.PROPERTY) annotation class JsonIgnore // Example use class Creature( @JsonIgnore val name: String, @JsonName("att") val attack: Int, @JsonName("def") val defence: Int, val traits: List<Trait>, val cost: Map<Element, Int> ) enum class Element { FOREST, ANY, } enum class Trait { FLYING } fun main() { val creature = Creature( name = "Cockatrice", attack = 2, defence = 4, traits = listOf(Trait.FLYING), cost = mapOf( Element.ANY to 3, Element.FOREST to 2 ) ) println(creature.toJson()) // {"att": 2, "cost": {"ANY": 3, "FOREST": 2}, // "def": 4, "traits": ["FLYING"]} }
objectToJson function. To ignore properties, we will add a filter on the properties list. For each property, we need to check if it has the JsonIgnore annotation. To check if a property has this annotation, we could use the annotations property, but we can also use the hasAnnotation extension function on KAnnotatedElement. To respect a name change, we need to find the JsonName property annotation by using the findAnnotation extension function on KAnnotatedElement. This is how our function needs to be modified to respect both annotations:private fun objectToJson(any: Any) = any::class .memberProperties .filterNot { it.hasAnnotation<JsonIgnore>() } .joinToString( prefix = "{", postfix = "}", transform = { prop -> val annotation = prop.findAnnotation<JsonName>() val name = annotation?.name ?: prop.name "\"${name}\": ${valueToJson(prop.call(any))}" } )