比较Kotlin中可比较的列表

Jan*_*lek 11 generics 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))
Run Code Online (Sandbox Code Playgroud)

这应该打印-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
}
Run Code Online (Sandbox Code Playgroud)

这实际上编译并按预期工作,但有一些我很困惑的事情.

我的第一次尝试是使用以下方法签名:

fun compare(list1: List<Comparable<*>>, list2: List<Comparable<*>>): Int
Run Code Online (Sandbox Code Playgroud)

但这并没有编译.这是为什么?这个宣言与另一个宣言有何不同?

其次,如果我尝试在匹配的位置比较具有无比值的列表,我会得到一个类型转换错误.例如,当比较[1,1]和[1,"abc"]时,我得到了

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Run Code Online (Sandbox Code Playgroud)

这显然出现在投下的类型中

elem1.compareTo(elem2 as T)
Run Code Online (Sandbox Code Playgroud)

令我困惑的是:T如何在这里解决整数问题?事实上,我很惊讶这实际上是编译的.

第三,有没有办法摆脱未经检验的演员阵容?我试过了

if (elem2 !is T)
    // throw Exception
Run Code Online (Sandbox Code Playgroud)

但那没有编译.为什么?似乎不知怎的,在这个迭代中T被认为是整数,所以为什么我不能对它进行类型检查呢?

Ily*_*lya 15

Comparable是一个与其类型参数不一致的接口T.类型的值T仅允许在in-positions处,即作为类方法的参数而不是返回值.

interface Comparable<in T> {
    abstract operator fun compareTo(other: T): Int
}
Run Code Online (Sandbox Code Playgroud)

逆变型的星形投影等同于参数化的类型Nothing,因此Comparable<*>实际上是一个Comparable<in Nothing>.这意味着一旦你有一个未知类型的可比较,你就无法安全地将它与除了Nothing类型值之外的任何东西进行比较,而这个值已知没有值.:)

如果你试图将a Int与a 进行比较,你可能会遇到这种不安全的后果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)
}
Run Code Online (Sandbox Code Playgroud)

注意,由于泛型中的类型擦除确保值具有相同的类(elem1.javaClass == elem2.javaClass)并不意味着可以安全地比较值.例如List<Int>,List<String>两者都有相同的类List.