in kotlin ~ read.
Неочевидные заметки #2

Неочевидные заметки #2

На днях один из моих коллег задал вопрос в своей заметке: почему в Java 8 можно принять лямбду как компаратор, а в Kotlin - нет.

В качестве примера был приведен вот такой код:

Java:  
IntStream.range(0, 10).boxed().max((o1, o2) -> 0) 

Kotlin:  
(0..10).maxWith { o1, o2 -> 0 }  // не компилируется :(
(0..10).maxWith(Comparator { o1, o2 -> 0 }) 

Основной вопрос тут в том, что тот же maxWith мог бы просто принять лямбду, вместо того, чтобы принимать объект, который умеет принимать лямбду. Да, в Kotlin есть разный сахарок для этих случаев. Например, maxBy:

//primitives
listOf(4, 6, 5).maxBy { it }

//objects
listOf(obj1, obj2, obj3).maxBy { it.property }  

Или вариант с предопределённым компаратором, который сначала выдаст нули, а потом как-то отсортирует в natural order:

listOf(obj1, obj2, obj3).maxWith(nullsFirst())  

Последнее к слову никак не фигурирует в названии этой inline-функции. На засыпку вопрос - как сортирует nullsLast: в натуральном порядке или реверсивном?

В общем, мне стало интересно, а можно ли исправить этот недочёт и дать возможность заиспользовать лямбду без обертки её в объект? На помощь спешат extension-функции.

Итак, для коллекций с их помощью можно задекларировать новый метод max (да, метод с таким названием у нас уже есть, но он ничего не принимает), в который мы передадим функцию сравнения двух объектов из коллекции:

public fun <E> Collection<E>.max(function: (o1: E, o2: E) -> Int): E? {  
    return this.maxWith(kotlin.Comparator(function))
}

Да, внутри мы всё равно заиспользовали компаратор, но для внешнего наблюдателя вызов теперь будет выглядеть так:

listOf(4, 6, 5).max { o1, o2 -> 0 }  

Согласитесь, что такая конструкция читается немного приятнее, чем в варианте с компаратором.

В исходной задаче у нас был IntRange, и поэтому нам нужно добавить такой же метод для него:

public fun IntRange.max(function: (o1: Int, o2: Int) -> Int): Int? {  
    return this.maxWith(kotlin.Comparator(function))
}

То же самое можно проделать и с sort:

listOf(4, 6, 5).sort { o1, o2 -> 0 }

public fun <E> Collection<E>.sort(function: (o1: E, o2: E) -> Int): Collection<E> {  
    return this.sortedWith(kotlin.Comparator(function))
}

Вопрос "Почему так нельзя было сделать с самого начала остаётся?" - риторический.

comments powered by Disqus