Kotlin: Как работать со списками: Непроверено В ролях: kotlin.collections.List <Kotlin.Any?> To kotlin.colletions.List <Путевая точкa>
Я хочу написать функцию, которая возвращает каждый элемент в 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>
я получаю предупреждение:
Unchecked Cast: kotlin.collections.List to kotlin.colletions.List
Я не могу найти способ реализовать его в противном случае. Какой правильный способ реализовать эту функцию без этого предупреждения?
Ответы
Ответ 1
В 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 { ... }
line), поэтому он не будет заметным.
-
Напишите функцию утилиты, которая проверяет тип и возвращает тот же список, если тип верен, тем самым инкапсулируя листинг (все еще не установленный с точки зрения компилятора) внутри него:
@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
Ответ 2
В случае родовых классов отбрасывание не может быть проверено, поскольку информация о типе стирается во время выполнения. Но вы проверяете, что все объекты в списке Waypoint
, поэтому вы можете просто подавить предупреждение с помощью @Suppress("UNCHECKED_CAST")
.
Чтобы избежать таких предупреждений, вы должны передать List
объектов, конвертируемых в Waypoint
. Когда вы используете *
, но, пытаясь получить доступ к этому списку в виде типизированного списка, вам всегда понадобится листинг, и этот прилив будет снят.
Ответ 3
Чтобы улучшить @hotkey, ответьте здесь на мое решение:
val waypointList = list.filterIsInstance<Waypoint>().takeIf { it.size == list.size }
Это дает вам List<Waypoint>
если все элементы могут быть отлиты, в противном случае - null.
Ответ 4
Я сделал небольшое изменение в ответе @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
Ответ 5
Вместо
myGenericList.filter { it is AbstractRobotTurn } as List<AbstractRobotTurn>
Мне нравится делать
myGenericList.filter { it is AbstractRobotTurn }.map { it as AbstractRobotTurn }
Не уверен, насколько это эффективно, но по крайней мере никаких предупреждений.