Using comparator in kotlin

13,235

Solution 1

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
        }
    }

Solution 2

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)

Solution 3

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()!

Solution 4

There is 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 copy of list.

Share:
13,235

Related videos on Youtube

Abraham George
Author by

Abraham George

Updated on June 04, 2022

Comments

  • Abraham George
    Abraham George almost 2 years

    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;
            } 
        };

  • omilke
    omilke almost 3 years
    +1 for not only discussing the actual question but pointing out the basic requirements for Comparable and including Kotlin std lib.