Добавить в корзинуПозвонить
Найти в Дзене

Разница между * и Any в Kotlin

Разбираю у себя закладки и нашла интересный вопрос: какая разница между Generic<*> и Generic<Any>? Я помню, что когда-то его задал мне мой джуниор, и я тогда очень радовалась, что мы работаем дома и было время погуглить. Мой примерный ответ ниже. :) Сначала вопрос кажется каким-то очевидным, а потом начинаешь думать и понимаешь, что всё не так уж просто и одним предложением не отделаться. Мы ведь в оба дженерика можем передать что угодно. Давайте немного вспомним Java. Там есть такая штука как List<? extends Number> — это означает, что список принимает какие-то элементы, где родитель Number. И еще есть List<? super String> — список принимает родителей String. (Пришлось даже открыть философию Java и посмотреть правильное написание. С этим котлином уже всё забылось. :D). Я написала совсем простой формулировкой, чтобы не углубляться в дженерики. Да, тут еще можно было бы рассказать про ковариантность, производителей и потребителей, но не буду. Так вот в Kotlin есть аналоги, но с более

Разбираю у себя закладки и нашла интересный вопрос: какая разница между Generic<*> и Generic<Any>? Я помню, что когда-то его задал мне мой джуниор, и я тогда очень радовалась, что мы работаем дома и было время погуглить. Мой примерный ответ ниже. :)

Сначала вопрос кажется каким-то очевидным, а потом начинаешь думать и понимаешь, что всё не так уж просто и одним предложением не отделаться. Мы ведь в оба дженерика можем передать что угодно.

Давайте немного вспомним Java. Там есть такая штука как List<? extends Number> — это означает, что список принимает какие-то элементы, где родитель Number. И еще есть List<? super String> — список принимает родителей String. (Пришлось даже открыть философию Java и посмотреть правильное написание. С этим котлином уже всё забылось. :D). Я написала совсем простой формулировкой, чтобы не углубляться в дженерики. Да, тут еще можно было бы рассказать про ковариантность, производителей и потребителей, но не буду.

Так вот в Kotlin есть аналоги, но с более простым написанием: List<in T> и List<out T>. И у них есть ограничения, потому что один из листов производитель, а второй потребитель. Что означает, что один из листов доступен только для чтения, а второй уже для записи. Сделано это для безопасного использования.

Но что еще более интересно, есть третий вариант — List<*>. И он идеально подходит, когда мы совершенно ничего не знаем о типе, но хотим безопасно использовать дженерики.

Давайте сразу на примере посмотрим. Предположим есть два метода: fun insertAnyList(list: List<Any?>) и fun insert(list: List<*>). Теперь попробуем вызвать методы и передать в них лист из строк. Всё получится успешно. Никаких ошибок. Пока что разницы нет.

А теперь давайте сделаем кое-что интересное и изменим наш List на Array.

-2

И совершенно внезапно мы видим, что insertAnyList подчеркивается красным и не хочет работать. Студия показывает ошибку: Type mismatch. Required: Array<Any?> Found: Array<String>. Но почему?

Дело в том, что сейчас от нас ждут, что мы передадим именно тип Any?, а не что-либо другое. Почему же работало с листами? Давайте перейдем внутрь и посмотрим.

-3

Тадам. На самом деле, когда мы пишем List<Any>, то под капотом это преобразовывается в List<out Any>. И это объясняет, почему работает с листами, а с массивами, внезапно всё сломалось.

Из всего вышеперечисленного приходим к ответу:

Generic<*> — создан для безопасного использования, где мы сможем спокойно и записывать что-то и читать. Сюда мы передаём что-то конкретное. Кстати, можем даже тип Any передать, но не знаю, когда это может пригодиться. Вы можете самостоятельно в студии создать метод fun insert(list: Array<*>) {} и попробовать передавать в него различные списки и посмотреть, как тип автоматически определяется. Выглядит интересно.

Generic<Any> — сюда надо передавать именно с типом Any, а это означает, что ничто не мешает нам передать что-то в стиле arrayOf<Any>(1, 2, "Anna", 'a'), но тип везде разный и постоянно придется кастовать к чему-либо. Даже если мы просто передадим список из одних строк, то это будет тип Any, а не String, что накладывает кучу ограничений. Банально, мы не сможем использовать методы для String без каста.