在科特林中使用比较器

Using comparator in kotlin

我是 kotlin 新手,如何使用 Collections

比较对象

Collections.sort(list,myCustomComparator)

我们如何在 kotlin 中编写 MyCustomComparator 方法?

private final Comparator<CustomObject> myCustomComparator = (a, b) -> {
        if (a == null && b == null) {
            return 0;
        } else if (a == null) {
            return -1;
        } else if (b == null) {
            return 1;
        } 
    };

这几乎与 Java 中的方法相同:

private val myCustomComparator =  Comparator<CustomObject> { a, b ->
    when {
        (a == null && b == null) -> 0
        (a == null) -> -1
        else -> 1
    }
}

if else if else ... 被单个 Kotlin when 取代,以提高代码的可读性。

在 Kotlin 中使用 Comparator 对列表进行排序也可以这样写:

val customObjects = listOf(CustomObject(), CustomObject())
customObjects.sortedWith(myCustomComparator)

您可以将 SAM conversion 与 lambda 一起使用(因为 Comparator 是一个 Java 接口,Kotlin 将允许您这样做)或匿名 class 对象.

使用 lambda 它将如下所示:

val customComparator = Comparator<CustomObject> { a, b ->
    if (a == null && b == null) {
        return 0;
    } else if (a == null) {
        return -1;
    } else if (b == null) {
        return 1;
    }
}

和匿名class版本:

val customComparator = object: Comparator<CustomObject> {
    override fun compare(a: CustomObject, b: CustomObject): Int {
        if (a == null && b == null) {
            return 0;
        } else if (a == null) {
            return -1;
        } else if (b == null) {
            return 1;
        }
    }
}

在 Kotlin 中有更好的方法对集合进行排序 - 您可以像这样使用扩展函数 sortedWith

list.sortedWith(Comparator { s1, s2 ->
    when {
        s1 == null && s2 == null -> 0
        s1 == null -> -1
        else -> 1
    }
})

但请记住,这将 return 复制 列表。

根据其他答案,相当直接的翻译可让您对列表进行排序,例如:

fun myCustomComparator() = Comparator<CustomObject>{ a, b ->
    when {
        (a == null && b == null) -> 0
        (a == null) -> -1
        else -> 1
    }
}

现在,这里没有任何内容取决于您的 CustomObject。所以让它变得通用是微不足道的,所以它可以处理任何类型:

fun <T> nullsFirstComparator() = Comparator<T>{ a, b ->
    when {
        (a == null && b == null) -> 0
        (a == null) -> -1
        else -> 1
    }
}

但是,这里存在一些潜在的问题。

主要是不一致Comparator 的一般合同在 Java docs:

中详细说明

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y

(不幸的是,Kotlin docs 没有提到任何这些。真可惜他们没有达到 Java 的标准。)

然而,上面的比较器并没有这样做;如果 a 和 b 非空,则 compare(a, b)compare(b, a) 都是 1!

这很可能会导致问题;例如,根据 sort() 方法的编码方式,它可能会使列表未排序,或者永远不会完成。或者,如果您将它用于排序的地图,地图可能无法 return 它的某些值,或者永远不会完成。

这可以通过添加第四个案例来解决:

fun <T> nullsFirstComparator() = Comparator<T>{ a, b ->
    when {
        (a == null && b == null) -> 0
        (a == null) -> -1
        (b == null) -> 1
        else -> 0
    }
}

现在比较器是一致的;空值总是在非空值之前。

但它仍然有一个不受欢迎的特性:所有非空值现在都被视为等价的,并且不能在它们内部进行排序。一般来说,没有办法解决这个问题,因为 Kotlin 不知道如何比较两个任意对象的顺序。但是有两种方法可以告诉它如何。

一种方法是将其限制为具有自然顺序的对象,即实现 Comparable interface.  (Once again, the Java docs 的对象可以更好地解释这一点。)

fun <T : Comparable<T>> nullsFirstComparator() = Comparator<T>{ a, b ->
    when {
        (a == null && b == null) -> 0
        (a == null) -> -1
        (b == null) -> 1
        else -> a.compareTo(b)
    }
}

但是,如果您使用标准库 kotlin.comparisons.compareValues() 函数,则可以简化:

fun <T : Comparable<T>> nullsFirstComparator()
    = Comparator<T>{ a, b -> compareValues(a, b) }

另一个是自己提供一个排序——你可以通过提供另一个 Comparator来处理非空比较:

fun <T> nullsFirstComparator(comparator: Comparator<T>) = Comparator<T>{ a, b ->
    when {
        (a == null && b == null) -> 0
        (a == null) -> -1
        (b == null) -> 1
        else -> c.compare(a, b)
    }
}

但是你不需要自己写,因为 Kotlin 标准库中已经有了它,因为 kotlin.comparisons.nullsFirst()!

考虑@Alexander的回答后,代码可以写成

private val MyCustomComparator = Comparator<MyObject> { a, b ->
        when {
            a == null && b == null -> return@Comparator 0
            a == null -> return@Comparator -1
            b == null -> return@Comparator 1
          
            else -> return@Comparator 0
        }
    }