Разница между `*` и `Any` в дженериках Kotlin: звездочка vs Any
Ключевое отличие: * — это неизвестный тип, Any — это конкретный тип
Отличный вопрос! Это одна из самых частых ловушек для новичков в Kotlin. Давайте разберемся раз и навсегда.
Главная идея: MutableList означает "список, который может содержать только элементы типа Any (а Any — это супертип для всех типов в Kotlin, поэтому туда можно положить что угодно)". А MutableList<*> означает "список, тип элементов которого неизвестен". Это принципиально разные вещи.
Практический пример: запись
С MutableList вы можете положить любой элемент:
val listAny: MutableList<Any> = mutableListOf("строка", 42)
listAny.add(3.14) // ✅ Работает
listAny.add("новая") // ✅ Работает
listAny.add(true) // ✅ Работает
А с MutableList<*> вы не можете положить ничего (кроме null):
val listStar: MutableList<*> = mutableListOf("строка", 42)
listStar.add(3.14) // ❌ Ошибка компиляции!
listStar.add("новая") // ❌ Ошибка!
listStar.add(null) // ✅ Только null можно
Почему так? Потому что компилятор не знает, какой конкретный тип у listStar. Может быть, на самом деле это MutableList? Если бы мы добавили туда число, мы бы сломали типы. Поэтому Kotlin запрещает добавление любых элементов (кроме null, который можно добавить в любой список).
Практический пример: чтение
При чтении все наоборот — * дает больше свободы:
val listAny: MutableList<Any> = mutableListOf("строка", 42)
val itemFromAny: Any = listAny[0] // ✅ Всегда Any
val listStar: MutableList<*> = mutableListOf("строка", 42)
val itemFromStar: Any? = listStar[0] // ✅ Можно присвоить Any? (но не Any!)
val itemFromStarString: String? = listStar[0] as? String // ✅ Только с безопасным приведением
При чтении из MutableList<*> вы получаете Any? (так как элемент может быть null, и тип неизвестен). А из MutableList — гарантированно Any (не nullable).
Когда что использовать?
- Используйте
Any, когда вы хотите создать коллекцию, в которую можно класть разные типы, и вы будете активно добавлять туда элементы. - Используйте
*, когда вы только читаете данные из коллекции, и вам не важно, какой именно тип там хранится. Например, функция, которая печатает все элементы списка:
fun printAll(items: List<*>) { // Лучше, чем List<Any>
for (item in items) {
println(item)
}
}
Здесь List<*> лучше, потому что вы можете передать List, List или List — функция будет работать со всеми. А если бы вы написали List, то передать List не получилось бы (дженерики в Kotlin инвариантны по умолчанию).
Итоговая таблица
MutableList<Any>— можно читать (получаемAny) и писать (любые элементы).MutableList<*>— можно читать (получаемAny?), но писать нельзя (кроме null).List<*>— идеально для read-only коллекций, когда тип не важен.
Надеюсь, теперь стало понятнее! Звездочка — это не "любой тип", а "неизвестный тип", и это накладывает ограничения на запись, но расширяет возможности для чтения.
<<Дополнение: вариант с out-проекцией
Коллега выше отлично объяснил разницу. Я добавлю альтернативный взгляд: звездочка * — это синтаксический сахар для out Any?. То есть MutableList<*> эквивалентен MutableList.
Это объясняет, почему мы можем только читать, но не писать: out-проекция означает, что тип используется только на выходе (в return-типах методов), но не на входе (в параметрах методов).
Еще один полезный пример
Представьте, что у вас есть функция, которая принимает Map:
fun printValues(map: Map<String, *>) {
for ((key, value) in map) {
println("$key = $value")
}
}
fun main() {
val stringMap: Map<String, String> = mapOf("a" to "hello")
val intMap: Map<String, Int> = mapOf("b" to 42)
printValues(stringMap) // ✅ Работает
printValues(intMap) // ✅ Работает
}
Если бы мы написали Map, то передать Map не получилось бы без явного приведения. Поэтому * — это элегантный способ сказать: "мне все равно, какой тип значений, я их только читаю".
В Kotlin принято использовать * для read-only коллекций и Any только когда реально нужно хранить разнородные данные и добавлять их.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги