如何使用 Reflect 和安全转换实现 Kotlin 比较器?

问题描述 投票:0回答:1

我想实现一个函数,根据两个相同类型的对象的

Comparable
属性来比较它们,例如:

fun <T:Any> reflectCompare(a: T, b: T) : Int {
    a::class
        .memberProperties
        .forEach { prop ->
            val pa = prop.getter.call(a)
            if(pa is Comparable<*>) {
                val cmp = pa.compareTo(prop.getter.call(b)) // ERROR: Type mismatch
                if(cmp != 0) return cmp
            }
        }
    return 0
}

但是,如何在不诉诸不安全的转换的情况下实现这一目标

Comparable<Any>

  1. 例如,在上面的实现中,使用

    prop.getter.call(b)
    作为
    pa.compareTo(...)
    的参数会产生
    Type mismatch: inferred type is Any? but Nothing was expected
    错误。

  2. 此外,用

    if(pa is Comparable<*>)
    替换
    if(pa is Comparable<Any?>)
    是不可行的。由于类型擦除,无法使用
    is
    检查类型参数。

  3. 即使

    filterIsInstance
    看起来安全并且编译没有错误,但它并不完全安全。尽管它的外观如此,但它并不真正安全。

以下

reflectCompare
的替代实现编译时没有错误,但无法提供预期的行为:

fun <T:Any> reflectCompare(a: T, b: T) : Int {
    a::class
        .memberProperties
        .filterIsInstance<KProperty1<T, Comparable<Any?>>>()
        .forEach { prop ->
            val pa = prop.getter.call(a)
            val cmp = pa.compareTo(prop.getter.call(b))
            if(cmp != 0) return cmp
        }
    return 0
}

如果与具有不可比较属性的类一起使用,它将尝试将该属性的值转换为

Comparable
,从而导致错误。例如,以下测试失败并显示:
ClassCastException: class Residence cannot be cast to class java.lang.Comparable

class Individual(val name: String, val addr: Residence)
class Residence(val nr: Int, val street: String)

@Test fun testCompareReflect() {
    val a = Individual("Bart", Residence(45, "Pink Street"))
    val b = Individual("Bart", Residence(23, "Soho"))
    compare(a, b) // ClassCastException: class Residence cannot be cast to class java.lang.Comparable
}

在检查

filterIsInstance
的实现后,很明显它并没有真正提供预期的行为。它使用
if (element is R)
,其中
R
采用上面示例中的
KProperty1<T, Comparable<Any?>>
。由于类型擦除,它仅检查它是否是
KProperty
,忽略类型参数。也许
filterIsInstance
应该警告这种有偏差的行为,但是类型参数
@kotlin.internal.NoInfer R
上的
R
掩盖了这个错误。

kotlin generics type-erasure kotlin-reflect
1个回答
0
投票

您的代码(至少)存在两个主要问题。

首先,它在概念上是错误的。仅因为该属性是

Comparable<*>
,并不意味着我们可以将其与第二个对象中的相同属性进行比较。它可以与任何东西进行比较,但不一定要与它本身进行比较:

fun main() {
    val o1 = MyClass()
    val o2 = MyClass()
    o1.prop1.compareTo(o2.prop1) // doesn't compile
}

class MyClass {
    val prop1: Comparable<String> = TODO()
}

我们宁愿需要检查

Comparable
的类型参数并验证属性是否属于同一类型。然后我们就可以安全地施放了。尽管如此,仍然存在潜在的极端情况,例如如果
T
Comparable
被删除,我们无法轻易验证它。

其次,您假设

a
b
属于同一类型,但这不一定正确。如果
a
b
的子类型并添加了额外的属性怎么办?在这种情况下,
prop.getter.call(b)
将会因为缺少道具
b
而失败。您可以添加运行时检查
a::class == b::class
或者仅比较超类型的属性 - 这取决于您的情况。

© www.soinside.com 2019 - 2024. All rights reserved.