We’ve already posted an article about Kotlin generics, so I’m sure you already have a good understanding of generics. The key and the difficult part of this section is to understand the variant, and today we’re going to show you a bit of code with a hole in it. This article requires a relatively deep understanding of generics and reflection, anyway. I accept no responsibility for any discomfort caused during the reading. 🙂 to escape

1. Pits come from afar.

We have a very simple requirement to add a description method for many classes. The return value of this method is the attribute name and value of the class, such as the following class:

class Person(val name: String. val age: Int)Copy the code

Its description method should return something like this:

age: 30; name: BennyCopy the code

This thing is very general, so we decided to use the extension method plus reflection to output, so:

inline fun <reified T : Any> T.description()
        = this: :class.memberProperties

       .map {            "The ${it.name}: The ${it.get(this@description)}"        }        .joinToString(separator = ";")Copy the code

It looks nice, but this code doesn’t compile! Why is that? It. Get (this@description) is a reflection reference to an attribute, passing it to the caller this to get the value of the current attribute. However, it doesn’t work, it’s an error.

2. The pit also makes sense

The compiler says this@description is a Person, and it. Get requires out Person.

In line with the attitude of appeasement, type does not match I strong turn not to get:

it.get(this@description as out Person) // Error!!Copy the code

But the problem is you old people carefully look, covariant type strong turn thing, really never heard of..

That’s interesting. I’m using an instance of Person, but the generic argument to the property is out Person. Let’s look at the definition of the reflection reference type for the following attributes (i.e., the type of IT) :

public interface KProperty1<T. out R> : KProperty<R>. (T) -> R { 

    public fun get(receiver: T): R    
    ..

}Copy the code

Where does the type of T come from? This ::class is the object that gets the reflection reference from KClass . No, KClass

! It may seem strange, but if you think about it, does it make sense?

val person: Any = Person("Benny". 30)Copy the code

In this case, if person::class returns KClass

, we will get nothing in subsequent reflection operations to access the property, since Any has no property at all. Person ::class should get the real type of the object. Yes, in order to accommodate this and not let the type system fail, Kotlin chose to fix the problem by setting the person::class type to KClass

.

Java actually have similar operation, please see the article: in Java getClass method return value type (https://zhuanlan.zhihu.com/p/27012082?utm_source=qq&utm_medium=social)

Person ::class is equivalent to Java’s Person.getClass (), although this method is signed like this:

public final Class<?> getClass()Copy the code

But the return value is actually covariant:

Class<? extends String> c = "".getClass();Copy the code

However, unlike Kotlin, Java’s reflection parameter requirements are very simple, there is no strict type restriction, as long as the Object is accepted:

Method.java

public Object get(Object obj) Copy the code

To summarize, Java and Kotlin handle person.getClass() (Java) or Person ::class (Kotlin) the same way. The return value is covariant, but for reflection, Java has very little requirement for parameter types. Kotlin is very strict, and the problem with that is that Kotlin’s reflection is uncomfortable to use.

In Reflection and Kotlin’s official forum, some people expressed similar doubts about this: How could Kotlin go so far as to give me a covariant error in such a perfect code?

Error:(18, 16) Kotlin: Out-projected type ‘KMutableProperty1’ prohibits the use of ‘public abstract fun set(receiver: T, value: R): Unit defined in kotlin.reflect.KMutableProperty1’

Reflection, originally is black technology, type whole so complex really necessary?

3. Pit filling

It’s not a good code farmer to leave a hole unfilled.

It’s such a big hole that, frankly, I’m embarrassed to write this article without a solution.

3.1 Type Conversion Scheme

Who says type coercion is bad? Who said that? Since get doesn’t work, we’ll give it a Java reflection like version with no restrictions on parameter types:

fun <T. R> KProperty1<T. R>.getUnsafed(receiver: Any): R {
    return get(receiver as T)

}Copy the code

So our t.description extension can be modified slightly:

inline fun <reified T : Any> T.description()
        = this: :class.memberProperties

       .map {            "The ${it.name}: The ${it.getUnsafed(this@description)}" // Notice the changes here        }        .joinToString(separator = ";")Copy the code

No problem.

3.2 Java Reflection Scheme

Kotlin reflection doesn’t work? Need not also not line yao, can’t afford to fight can’t afford to hide, what the world.

inline fun <reified T : Any> T.description()
        = this.javaClass.declaredFields

       .map {            // Note that the Kotlin property we access is private for Java and the getter is public            it.isAccessible = true            "The ${it.name}: The ${it.get(this@description)}"        }        .joinToString(separator = ";")Copy the code

3.3 My friends and I were shocked by the plan

This.. What the hell.

I don’t know about this, but there’s more than one way that kotlin objects get KClass instances. There’s another way that kotlin objects get KClass instances: This.javaclass. Kotlin, this type is KClass , no out, repeat, no out!

So our code could also look like this:

inline fun <reified T : Any> T.description()
        = this.javaClass.kotlin.memberProperties

       .map {            "The ${it.name}: The ${it.get(this@description)}"        }        .joinToString(separator = ";")Copy the code

Wow, official Kotlin developers, you guys are so unkind.

I was surprised to find that there was a significant difference between the two methods of getting kclasses, but I checked the source code. Yes, this::class can be forced to jump to the call stack when debugging. KClass instance = KClass instance ();

public class Reflection {

    public static KClass getOrCreateKotlinClass(Class javaClass) {
        return factory.getOrCreateKotlinClass(javaClass);
    }

    ...

}Copy the code

KClass

, KClass , KClass

I’m just asking, does it hurt your conscience?

4. Summary

This article tells a story about some cases where reflection code does not compile due to the strictness of Kotlin generic types. This story, you say Kotlin is a lot of things also ok, say it is rigorous also ok, anyway, the solution we have, big deal, big deal I go to the overpass film. What stupid code? I’m done with it!