Intereting Posts
Безглавая однопользовательская библиотека для JVM и JavaScript Kotlin: Как я могу создать «статическую» наследуемую функцию? Использование синтетических свойств Котлина Kotlin @JvmStatic и случайное переопределение в сопутствующем объекте Пользовательский полу-обобщенный Factory для ViewModels Покрытие кода Котлина в конвейере CI Возможность аутентификации пользователя в режиме Firebase, но не может хранить данные в базе данных Firebase Как «продолжить» или «ломать» в выражении `when` внутри цикла while, используя Kotlin Расширение недвижимости Котлин Как проверить, была ли инициализирована переменная «lateinit»? Android Kotlin – Как настроить методы настройки Java-конструктора kotlin работает с ресурсами (массив и параметр функции) kotlin map based properties и Jackson не работают – делегируйте точки на другую карту, чтобы свойство карты Kotlin не может вывести тип при использовании ссылки метода в Flowable Spring Boot ThymeLeaf и Kotlin – значение флажка не передается контроллеру?

Сравнение списков сопоставимых данных в Котлине

Я пытаюсь написать функцию, которая сравнивает два списка сравниваемых. Сравниваемые могут быть разных типов, если элементы в тех же позициях в сравниваемых двух списках сопоставимы. Пример:

val list1 = ArrayList<Comparable<*>>() val list2 = ArrayList<Comparable<*>>() list1.add(10) list1.add("xyz") list1.add('a') list2.add(10) list2.add("xyz") list2.add('b') println(compare(list1, list2)) 

Это должно печатать -1, потому что

  • 10 == 10
  • "xyz" == "xyz"
  • 'a' <'b'

и таким образом list1 <list2.

Вот код, который я собрал вместе с процессом проб и ошибок, так как я немного смущен тем, как дженерики работают в этом конкретном случае:

 fun <T> compare(list1: List<Comparable<T>>, list2: List<Comparable<T>>): Int { for (i in 0..Math.max(list1.size, list2.size) - 1) { val elem1 = if (i < list1.size) list1[i] else null val elem2 = if (i < list2.size) list2[i] else null if (elem1 == null && elem2 == null) return 0 if (elem1 == null) return -1 if (elem2 == null) return 1 @Suppress("UNCHECKED_CAST") val comparisonResult = elem1.compareTo(elem2 as T) if (comparisonResult != 0) return comparisonResult } return 0 } 

И это фактически компилируется и работает так, как ожидалось, но есть несколько вещей, о которых я озадачен.

Моя первая попытка заключалась в следующем сигнатуре метода:

 fun compare(list1: List<Comparable<*>>, list2: List<Comparable<*>>): Int 

Однако это не скомпилировалось. Почему это? И как это заявление отличается от другого?

Во-вторых, если я попытаюсь сравнить списки с несравнимыми значениями в соответствующих положениях, я получаю ошибку приведения типов. Например, при сравнении [1,1] с [1, "abc"], я получаю

 java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 

Это, по-видимому, возникает при типе

 elem1.compareTo(elem2 as T) 

Что меня озадачивает: как T решился на Integer? На самом деле, я удивлен, что это действительно компилируется.

И в-третьих, есть ли способ избавиться от неконтролируемого броска? Я пытался

 if (elem2 !is T) // throw Exception 

но это не скомпилировалось. Зачем? Кажется, что как-то известно, что T намеревается быть целым числом в этой итерации, поэтому почему я не могу проверить тип?

Solutions Collecting From Web of "Сравнение списков сопоставимых данных в Котлине"

Comparable является интерфейс, контравариантный по своему типу параметра T Значения типа T допускаются только in -позициях, а именно в качестве параметров методов класса, а не как возвращаемые значения.

 interface Comparable<in T> { abstract operator fun compareTo(other: T): Int } 

Звездная проекция контравариантного типа эквивалентна типу, параметризованному Nothing , поэтому Comparable<*> на самом деле является Comparable<in Nothing> . Это означает, что, как только у вас есть сопоставимый неизвестный тип, вы не можете безопасно сравнивать его ни с чем, кроме значения типа Nothing , которое, как известно, не имеет значений. 🙂

Вы можете столкнуться с последствиями такой небезопасности, если попытаетесь сравнить Int с String . Это не elem2 as T бросает ClassCastException (это действительно непроверенный актер, как предупреждение, которое вы подавляете), это реализация String.compareTo которая бросает, когда встречается с чем-то, что не является String .

Возвращаясь к вопросу, вы можете реализовать такое сравнение списков с помощью библиотечной функции kotlin.comparisons.compareValues . Он знает, как обрабатывать нули и скрывает отвратительный неконтролируемый бросок внутри.

 import kotlin.comparisons.* fun compareLists(list1: List<Comparable<*>>, list2: List<Comparable<*>>): Int { for (i in 0..Math.min(list1.size, list2.size)-1) { val elem1 = list1[i] val elem2 = list2[i] if (elem1.javaClass != elem2.javaClass) { TODO("Decide what to do when you encounter values of different classes") } compareValues(elem1, elem2).let { if (it != 0) return it } } return compareValues(list1.size, list2.size) } 

Заметьте, из-за стирания типа в generics, чтобы значения имели один и тот же класс ( elem1.javaClass == elem2.javaClass ), не обязательно означают, что значения можно было бы безопасно сравнивать. Например, List<Int> и List<String> имеют одинаковый List классов.