Как безопасно привести List<Any?> к List<Waypoint> в Kotlin? Unchecked Cast
Проблема unchecked cast в Kotlin
Вы столкнулись с классической проблемой дженериков в JVM. Kotlin, как и Java, стирает информацию о типе во время выполнения (type erasure). Это значит, что во время выполнения List<Any?> и List<Waypoint> выглядят как просто List. Поэтому прямое приведение data as List<Waypoint> технически возможно, но компилятор предупреждает, что не может проверить типы элементов во время компиляции — отсюда и "Unchecked cast".
Правильное решение: фильтрация и отображение (filterIsInstance)
Самый безопасный способ — не приводить список целиком, а пройти по каждому элементу и проверить его тип. Для этого в Kotlin есть отличная функция-расширение filterIsInstance<T>(). Она создаёт новый список, содержащий только элементы указанного типа.
import kotlin.reflect.typeOf
data class Waypoint(val latitude: Double, val longitude: Double, val name: String)
fun getData(): List<Any?> {
return listOf(
Waypoint(55.75, 37.62, "Москва"),
Waypoint(59.93, 30.31, "СПб"),
"Строка-шпион", // специально добавим элемент другого типа
12345
)
}
fun main() {
val data: List<Any?> = getData()
// ✅ Правильный способ: filterIsInstance
val waypoints: List<Waypoint> = data.filterIsInstance<Waypoint>()
println(waypoints)
// Вывод: [Waypoint(latitude=55.75, longitude=37.62, name=Москва), Waypoint(latitude=59.93, longitude=30.31, name=СПб)]
// "Строка-шпион" и 12345 отфильтрованы, так как не являются Waypoint
}
Когда нужна проверка всех элементов (requireNoNulls + map)
Если вы абсолютно уверены, что все элементы — это Waypoint, но компилятор не знает об этом, используйте комбинацию map с явным приведением и обработкой ошибок:
fun main() {
val data: List<Any?> = getData()
// ✅ Безопасное приведение с проверкой
val waypoints: List<Waypoint> = data.map { element ->
element as? Waypoint
?: throw IllegalArgumentException("Элемент $element не является Waypoint")
}
// Или более кратко:
val waypoints2: List<Waypoint> = data.mapNotNull { it as? Waypoint }
}
Продвинутый подход: inline-функция с reified
Если вы часто делаете такие преобразования, можно написать свою утилитную функцию с reified типом (доступно только для inline-функций):
inline fun <reified T> List<*>.castToTypedList(): List<T> {
return this.filterIsInstance<T>()
}
// Использование:
val waypoints: List<Waypoint> = data.castToTypedList<Waypoint>()
Что НЕ стоит делать
- Не используйте
asбез проверки — это может привести кClassCastExceptionво время выполнения. - Не подавляйте предупреждение с помощью
@Suppress("UNCHECKED_CAST")без веской причины — вы теряете защиту компилятора. - Не полагайтесь на type erasure — если в списке окажется элемент другого типа, программа упадёт.
Итог
В Kotlin принято работать с типами безопасно. Используйте filterIsInstance, если возможны посторонние элементы, или map с безопасным приведением as?, если вы уверены в данных. Так вы избежите сюрпризов во время выполнения и сохраните читаемость кода.
Альтернативный подход: изменение архитектуры
Проблема часто возникает, когда вы смешиваете типы в одном списке или получаете данные из нетипизированного источника. Рассмотрите возможность рефакторинга, чтобы избежать List<Any?> в принципе.
// Вместо возврата List<Any?> сделайте специализированный возврат
fun getWaypoints(): List<Waypoint> {
return listOf(
Waypoint(55.75, 37.62, "Москва"),
Waypoint(59.93, 30.31, "СПб")
)
}
// Если данные приходят из JSON, используйте сериализацию
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class Waypoint(val latitude: Double, val longitude: Double, val name: String)
fun parseWaypoints(jsonString: String): List<Waypoint> {
return Json.decodeFromString<List<Waypoint>>(jsonString)
// Здесь тип известен на этапе компиляции, никаких приведений
}
Используйте sealed классы, если возможны разные типы элементов:
sealed class DataItem {
data class WaypointItem(val w: Waypoint) : DataItem()
data class StringItem(val s: String) : DataItem()
}
fun getData(): List<DataItem> { /* ... */ }
// Обработка:
when (item) {
is DataItem.WaypointItem -> { /* работаем с waypoint */ }
is DataItem.StringItem -> { /* работаем со строкой */ }
}
Этот подход полностью исключает необходимость в unchecked cast и делает код самодокументируемым.