Abraham George
Abraham George

Reputation: 73

Using comparator in kotlin

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

Answers (5)

Cililing
Cililing

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

gidds
gidds

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

Abraham George
Abraham George

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

Alexander Egger
Alexander Egger

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

necauqua
necauqua

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

Related Questions