比较Kotlin中的可比较列表
我试图编写一个比较两个可比较列表的函数。 只要被比较的两个表中的相同位置上的元素是可比较的,可比较物就可以是各种类型的。 例:
val list1 = ArrayList<Comparable<*>>() val list2 = ArrayList<Comparable<*>>() list1.add(10) list1.add("xyz") list1.add('a') list2.add(10) list2.add("xyz") list2.add('b') println(compare(list1, list2))
这应该打印-1因为
- 10 == 10
- “xyz”==“xyz”
- 'a'<'b'
因此list1 <list2。
下面是我用一些试验和错误过程把代码放在一起,因为我对这个具体情况下泛型如何工作有点困惑:
fun <T> compare(list1: List<Comparable<T>>, list2: List<Comparable<T>>): Int { for (i in 0..Math.max(list1.size, list2.size) - 1) { val elem1 = if (i < list1.size) list1[i] else null val elem2 = if (i < list2.size) list2[i] else null if (elem1 == null && elem2 == null) return 0 if (elem1 == null) return -1 if (elem2 == null) return 1 @Suppress("UNCHECKED_CAST") val comparisonResult = elem1.compareTo(elem2 as T) if (comparisonResult != 0) return comparisonResult } return 0 }
而这实际上是按照预期编译和运行的,但有一些我感到困惑的事情。
我第一次尝试使用下面的方法签名:
fun compare(list1: List<Comparable<*>>, list2: List<Comparable<*>>): Int
这虽然没有编译。 这是为什么? 这个声明和另一个声明有什么不同呢?
其次,如果我尝试在匹配的位置比较具有不可比较值的列表,则会出现类型转换错误。 例如,当比较[1,1]到[1,“abc”]时,我得到了
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
这显然是出现在类型中
elem1.compareTo(elem2 as T)
什么令我困惑:T如何解决Integer在这里? 事实上,我很惊讶,这实际上编译。
第三,有没有办法摆脱不受控制的演员? 我试过了
if (elem2 !is T) // throw Exception
但没有编译。 为什么? 看来不知怎的,在这个迭代中,T被认为是Integer,为什么我不能对它进行类型检查呢?
Comparable
是与其类型参数T
不相容的界面。 类型T
值只能in
-positions中使用,即作为类方法的参数,而不能作为返回值。
interface Comparable<in T> { abstract operator fun compareTo(other: T): Int }
逆变类型的星形投影相当于使用Nothing
参数化的类型,因此Comparable<*>
实际上是一个Comparable<in Nothing>
。 这意味着一旦你有一个未知类型的Comparable,你就不能安全地将它与除了Nothing
类型的值以外的任何东西比较,而这个值已知没有值。 🙂
如果尝试将Int
与String
进行比较,可能会遇到这种不安全的后果。 它不是elem2 as T
抛出ClassCastException(它实际上是一个未经检查的强制转换,因为警告你已经禁止了这个状态),它是String.compareTo
的实现,当它遇到不是String
东西时抛出。
回到这个问题,你可以在库函数kotlin.comparisons.compareValues的帮助下实现这样的列表比较。 它知道如何处理空洞,并隐藏恶劣的不加控制的内部。
import kotlin.comparisons.* fun compareLists(list1: List<Comparable<*>>, list2: List<Comparable<*>>): Int { for (i in 0..Math.min(list1.size, list2.size)-1) { val elem1 = list1[i] val elem2 = list2[i] if (elem1.javaClass != elem2.javaClass) { TODO("Decide what to do when you encounter values of different classes") } compareValues(elem1, elem2).let { if (it != 0) return it } } return compareValues(list1.size, list2.size) }
请注意,由于泛型中的类型擦除确保值具有相同的类( elem1.javaClass == elem2.javaClass
)并不一定意味着可以安全地比较这些值。 例如, List<Int>
和List<String>
都具有相同的类List
。