我想实现一个函数,根据两个相同类型的对象的
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>
?
例如,在上面的实现中,使用
prop.getter.call(b)
作为 pa.compareTo(...)
的参数会产生 Type mismatch: inferred type is Any? but Nothing was expected
错误。
此外,用
if(pa is Comparable<*>)
替换 if(pa is Comparable<Any?>)
是不可行的。由于类型擦除,无法使用 is
检查类型参数。
即使
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
掩盖了这个错误。
您的代码(至少)存在两个主要问题。
首先,它在概念上是错误的。仅因为该属性是
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
或者仅比较超类型的属性 - 这取决于您的情况。