dimanche 6 août 2023

Kotlin reify is not getting the actual instance type

I'm trying to implement a ValueObject base class using Kotlin and DDD. As any value object must consider all attributes in the equals method, I decided to implement a single method using reflection, so no subclass will need to implement it.

My ValueObject class is as follows:

abstract class ValueObject {
    override fun equals(other: Any?) = when {
        this === other -> true
        other !is ValueObject -> false
        else -> deepEquals(other)
    }

    inline fun <reified T : Any> deepEquals(other: T): Boolean {
        val fields = T::class.java.declaredFields

        println(T::class)

        for (field in fields) {
            field.isAccessible = true
            val thisValue = field.get(this)
            val otherValue = field.get(other)
            if (thisValue != otherValue && (thisValue == null || !thisValue.equals(otherValue))) {
                return false
            }
        }
        return true
    }

    override fun hashCode() = hashFromReflection()

    private fun hashFromReflection(): Int {
        val fields = this::class.java.declaredFields
        var result = 17

        for (field in fields) {
            field.isAccessible = true
            val value = field.get(this)
            result = 31 * result + (value?.hashCode() ?: 0)
        }

        return result
    }

    protected abstract fun validate() : Notification
}

I implemented the following code to test:

class PhoneNumber (val code: String, val number: String) : ValueObject() {
    override fun validate(): Notification {
        return Notification()
    }
}

fun main() {
    val phoneNumber = PhoneNumber("33", "w")
    val other = PhoneNumber("3", "w")
    println(phoneNumber == other)
    println(phoneNumber.deepEquals(other))
}

However, the result is weird. If I call deepEquals directly, it works fine. But if I use == operator, it considers T as ValueObject (not PhoneNumber), finds no declaredFields, and gives me a wrong result:

class br.all.domain.common.ValueObject
true
class br.all.domain.common.PhoneNumber
false

What I'm doing wrong?





Aucun commentaire:

Enregistrer un commentaire