Kotlin: Как работать с приведением списка: Не отмечен Актерский состав: kotlin.collections.List ‹Kotlin.Any?› В kotlin.colletions.List ‹Waypoint›

Я хочу написать функцию, которая возвращает каждый элемент в List, который не является первым или последним элементом (промежуточной точкой). Функция получает в качестве входных данных общий List<*>. Результат должен быть возвращен, только если элементы списка относятся к типу Waypoint:

fun getViaPoints(list: List<*>): List<Waypoint>? {

    list.forEach { if(it !is Waypoint ) return null }

    val waypointList = list as? List<Waypoint> ?: return null

    return waypointList.filter{ waypointList.indexOf(it) != 0 && waypointList.indexOf(it) != waypointList.lastIndex}
}

При преобразовании List<*> в List<Waypoint> я получаю предупреждение:

Не отмечено флажком Приведение: kotlin.collections.List в kotlin.colletions.List

Я не могу придумать, как это реализовать иначе. Как правильно реализовать эту функцию без этого предупреждения?


person Lukas Lechner    schedule 12.04.2016    source источник


Ответы (5)


В Kotlin нет возможности проверить общие параметры во время выполнения в общем случае (например, просто проверить элементы List<T>, что является только особым случаем), поэтому приведение универсального типа к другому с другими универсальными параметрами вызовет предупреждение, если только приведение находится в пределах границ дисперсии.

Однако есть разные решения:

  • Вы проверили тип и совершенно уверены, что приведение безопасно. Учитывая это, вы можете подавить предупреждение с помощью @Suppress("UNCHECKED_CAST") .

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Используйте функцию .filterIsInstance<T>(), которая проверяет типы элементов и возвращает список с элементами переданного типа:

    val waypointList: List<Waypoint> = list.filterIsInstance<Waypoint>()
    
    if (waypointList.size != list.size)
        return null
    

    или то же самое в одном утверждении:

    val waypointList = list.filterIsInstance<Waypoint>()
        .apply { if (size != list.size) return null }
    

    Это создаст новый список желаемого типа (таким образом, избегая непроверенного приведения внутри), вводя небольшие накладные расходы, но в то же время он избавляет вас от итерации через list и проверки типов (в строке list.foreach { ... }), поэтому он выиграл ' не быть заметным.

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

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> List<*>.checkItemsAre() =
            if (all { it is T })
                this as List<T>
            else null
    

    С использованием:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null
    
person hotkey    schedule 12.04.2016
comment
Отличный ответ! Я выбираю решение list.filterIsInstance ‹Waypoint› (), потому что считаю его самым чистым решением. - person Lukas Lechner; 12.04.2016
comment
Обратите внимание: если вы используете filterIsInstance а исходный список содержит элементы другого типа, ваш код будет молча их отфильтровать. Иногда это то, что вы хотите, но иногда вы можете получить IllegalStateException или что-то подобное. В последнем случае вы можете создать свой собственный метод для проверки и затем применить: inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R } - person mfulton26; 12.04.2016
comment
Обратите внимание, что .apply не возвращает возвращаемое значение лямбда, он возвращает объект приема. Вы, вероятно, захотите использовать .takeIf, если хотите, чтобы опция возвращала нуль. - person bj0; 08.01.2018

Чтобы улучшить ответ @ hotkey, вот мое решение:

val waypointList = list.filterIsInstance<Waypoint>().takeIf { it.size == list.size }

Это дает вам List<Waypoint>, если все элементы могут быть применены, в противном случае - null.

person Adam Kis    schedule 15.05.2019

В случае общих классов приведения не могут быть проверены, потому что информация о типе стирается во время выполнения. Но вы проверяете, что все объекты в списке - это Waypoint, поэтому вы можете просто подавить предупреждение с помощью @Suppress("UNCHECKED_CAST").

Чтобы избежать таких предупреждений, вы должны передать List объектов, конвертируемых в Waypoint. Когда вы используете *, но пытаетесь получить доступ к этому списку как к типизированному списку, вам всегда понадобится приведение, и это приведение не будет отмечено.

person Michael    schedule 12.04.2016

Я немного изменил ответ @hotkey, когда он использовался для проверки объектов Serializable to List:

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> Serializable.checkSerializableIsListOf() =
        if (this is List<*> && this.all { it is T })
          this as List<T>
        else null
person Samiami Jankis    schedule 16.08.2019
comment
Искал подобное решение, но такая ошибка: Cannot access 'Serializable': it is internal in 'kotlin.io' - person daviscodesbugs; 04.09.2019
comment
Решение: в Android для передачи определенного пользователем объекта ваш класс должен реализовывать интерфейс Parcelable вместо интерфейса Serializable. - person Fortran; 30.11.2020

Вместо того

myGenericList.filter { it is AbstractRobotTurn } as List<AbstractRobotTurn>

Мне нравится делать

myGenericList.filter { it is AbstractRobotTurn }.map { it as AbstractRobotTurn }

Не уверен, насколько это эффективно, но по крайней мере никаких предупреждений.

person Ludvig Linse    schedule 17.09.2019