Как и в других ответах, достаточно прямой перевод позволяет сортировать список, например, с помощью:
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 :
Разработчик должен убедиться, что sgn(compare(x, y)) == -sgn(compare(y, x))
для всех x и y
(К сожалению, Kotlin документы не упоминают ничего из этого. Жаль, что они не соответствуют стандартам Java.)
Однако приведенный выше компаратор этого не делает; если a и b не равны NULL, тогда compare(a, b)
и compare(b, a)
равны 1!
Это может привести к проблемам; например, в зависимости от того, как закодирован метод sort (), он может оставить список несортированным или никогда не завершать работу. Или, если вы используете его для отсортированной карты, карта может не вернуть некоторые из ее значений или никогда не завершится.
Это можно исправить, добавив четвертый регистр:
fun <T> nullsFirstComparator() = Comparator<T>{ a, b ->
when {
(a == null && b == null) -> 0
(a == null) -> -1
(b == null) -> -1
else -> 0
}
}
Компаратор теперь согласован; нулевые значения всегда предшествуют ненулевым.
Но это все еще имеет нежелательную особенность: все ненулевые значения теперь обрабатываются как эквивалентные и не могут быть отсортированы внутри самих себя. В общем, исправить это невозможно, поскольку Котлин не знает, как сравнить порядок двух произвольных объектов. Но есть два способа узнать, как это сделать.
Один из способов - ограничить его объектами, имеющими естественный порядок, т. Е. Реализующими интерфейс Comparable
. (Еще раз, Java-документы объясняют это гораздо лучше.)
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()
!