比较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类型的值以外的任何东西比较,而这个值已知没有值。 🙂

如果尝试将IntString进行比较,可能会遇到这种不安全的后果。 它不是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