Reputation: 73
I'm new to kotlin, how to compare objects using Collections
Collections.sort(list,myCustomComparator)
How can we write a MyCustomComparator
method in kotlin?
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;
}
};
Upvotes: 6
Views: 24681
Reputation: 4753
There is a better way to sort collections in Kotlin - you can use extension function sortedWith
like this:
list.sortedWith(Comparator { s1, s2 ->
when {
s1 == null && s2 == null -> 0
s1 == null -> -1
else -> 1
}
})
But remember, this will return a copy of the list.
Upvotes: 2
Reputation: 18557
As per other answers, a fairly direct translation lets you sort a list with e.g.:
fun myCustomComparator() = Comparator<CustomObject>{ a, b ->
when {
(a == null && b == null) -> 0
(a == null) -> -1
else -> 1
}
}
Now, there's nothing in here that depends upon your CustomObject
. So it's trivial to make it generic, so it can handle any type:
fun <T> nullsFirstComparator() = Comparator<T>{ a, b ->
when {
(a == null && b == null) -> 0
(a == null) -> -1
else -> 1
}
}
However, there are some underlying problems here.
The main one is that it's inconsistent. The general contract for a Comparator
is spelled out in the Java docs:
The implementor must ensure that
sgn(compare(x, y)) == -sgn(compare(y, x))
for all x and y
(Unfortunately the Kotlin docs don't mention any of this. It's a real shame they're not up to the standard of the Java ones.)
However, the comparator above doesn't do this; if a and b are non-null, then compare(a, b)
and compare(b, a)
are both 1!
This is likely to lead to problems; for example, depending how a sort() method is coded, it might leave the list unsorted, or never finish. Or if you use it for a sorted map, the map might fail to return some of its values, or never finish.
This can be fixed by adding a fourth case:
fun <T> nullsFirstComparator() = Comparator<T>{ a, b ->
when {
(a == null && b == null) -> 0
(a == null) -> -1
(b == null) -> 1
else -> 0
}
}
The comparator is now consistent; null values always come before non-null ones.
But it still has an undesirable feature: all non-null values are now treated as equivalent, and can't be sorted within themselves. There's no way to fix this in general, as Kotlin doesn't know how to compare the order of two arbitrary objects. But there are two ways you could tell it how.
One way is to restrict it to objects that have a natural order, i.e. which implement the Comparable
interface. (Once again, the Java docs explain this far better.)
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)
}
}
However, you can simplify if you use the standard library kotlin.comparisons.compareValues()
function:
fun <T : Comparable<T>> nullsFirstComparator()
= Comparator<T>{ a, b -> compareValues(a, b) }
The other is to provide an ordering yourself — which you do by providing another Comparator
to handle the non-null comparisons:
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)
}
}
But you don't need to write that yourself, because that's already in the Kotlin standard library, as kotlin.comparisons.nullsFirst()
!
Upvotes: 3
Reputation: 73
After considering the @Alexander answer, the code can be written as
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
}
}
Upvotes: 1
Reputation: 5300
This can be done almost the same way as in Java:
private val myCustomComparator = Comparator<CustomObject> { a, b ->
when {
(a == null && b == null) -> 0
(a == null) -> -1
else -> 1
}
}
if else if else ...
was replaced by a single Kotlin when
in order make the code better readable.
In Kotlin sorting a list using a Comparator
can also be written like this:
val customObjects = listOf(CustomObject(), CustomObject())
customObjects.sortedWith(myCustomComparator)
Upvotes: 17
Reputation: 104
You can use either a SAM conversion with lambda (because Comparator
is a Java interface and Kotlin will allow you to do so) or an anonymous class object.
With lambda it will look like this:
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;
}
}
And anonymous class version:
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;
}
}
}
Upvotes: 0