Программирование – это все, что связано с абстракциями.
Вместо того, чтобы инструктировать машины двоичными файлами (1 и 0), мы создали абстракции более высокого уровня. Эти языки более высокого уровня (абстракции) облегчают нам инструктирование машин.
Во время выполнения машины должны выделять и освобождать память. Мы добавили типы в абстракции для управления тем, как распределять и освобождать память.
С Типами , его легко читать, понимать и отлаживать. Типы позволяют компиляторам выделять нужную память и гарантировать отсутствие неожиданностей во время выполнения.
Затем мы создали статически типизированные языки. В статически типизированном языке компиляторы заставляют вас иметь конкретные типы (ссылочные или примитивные) в (почти) каждом выражении.
Примечание: Примитивные типы – это такие типы, как Int, Float, Character и т.д., А ссылочные типы – это те, которые созданы вами.
✨ Типы потрясающие , верно ✨
Но что, если мне нужно реализовать Список как для целого числа , так и для Строка ?
Вы можете создать две отдельные реализации для обоих Целых чисел и Строка . Но это создает избыточный код, который сложнее поддерживать и отлаживать.
Дженерики помогут вам здесь.
Универсальное программирование – это стиль компьютерного программирования, в котором алгоритмы написаны в терминах типов, которые будут определены позже, которые затем создаются при необходимости для определенных типов, предоставляемых в качестве параметров. – Википедия
Дженерики добавляют еще один уровень абстракции. С помощью дженериков мы можем написать фрагмент кода, который является общим для различных типов.
Дженерики не позволяют нам писать повторяющийся код для каждого отдельного типа , который нам нужно реализовать. Мы предоставляем компилятору некоторую информацию об общем типе. Компилятор (или) среда выполнения сделает все остальное.
В некоторых языках, таких как C++, компилятор расширит общий код необходимой информацией о типе. Эта генерация кода происходит во время компиляции.
Например, если вы определили класс Список с универсальным типом T и список использованных для Строка и Целое число . Затем компилятор сгенерирует отдельный класс для обеих Строк и Целое число .
Но почему Тот ? T – это общий способ определения типа. Вы вполне можете использовать там любую букву. О! эй, T для типа .
✅ Сгенерированный код будет иметь более высокую производительность( ⚡️ ), потому что среда выполнения точно знает, чего ожидать и как работать с типами.
✅ Среде выполнения нет необходимости распаковывать значение при его использовании и вставлять его при возврате.
💥 Поскольку для каждого типа будет отдельный класс, этот метод будет генерировать раздутый код.
С другой стороны, в языках JVM компилятор полностью стирает информацию о типе.
Механизм, который использует Java, называется Стирание типов .
Подробнее об удалении типов читайте здесь
Компиляторы создадут класс без информации о типе в нем. Затем JVM во время выполнения использует приведение для получения и установки значения.
Таким образом, один и тот же класс используется для обеих Строк и Целое число .
✅ Это приводит к меньшему раздуванию кода и обратная совместимость .
💥 С другой стороны , это приводит к загрязнению кучи и меньшая производительность .
Чтобы справиться с этим, JVM предоставляет гарантию чугуна при определении универсального. Таким образом, дженерики в Java являются инвариантными по умолчанию.
Предположим, что Класс Animal является Супертип из класса Собака .
open class Animal class Dog: Animal()
Примечание: для расширения класса Animal нам нужно открыть класс животных. В Kotlin классы по умолчанию являются окончательными.
interface List{ fun getAll(): List }
Затем у нас есть общий Список определенный интерфейс. Интерфейс имеет один метод GetAll .
Теперь давайте создадим Список животных и Список классов, реализующих определенный универсальный интерфейс.
class AnimalList: List{ override fun getAll(): List { TODO("not implemented") } } class DogList: List { override fun getAll(): List { TODO("not implemented") } }
Ковариация без
Даже несмотря на то, что Animal является родительским классом Dog , Список<Животное> не является родительским классом Список<Собака> .
fun main() {
val dogList = DogList()
val d: List = dogList.getAll() // 💥 Type mismatch
}
Компилятор не позволит нам использовать Подтип вместо суперТипа . То есть типы Животное и Собака не являются ковариантными .
В Kotlin мы можем сообщить компилятору, чтобы он принял Подтип вместо суперТипа с out ключевое слово.
interface List{ fun getAll(): List }
Выход из ключевое слово сообщает компилятору, что Животное и Собака являются ковариантными типами, и их можно использовать взаимозаменяемо.
Теперь это сработает,
fun main() {
val dogList = DogList()
val d: List = dogList.getAll() // ✅
}
Используйте out для неизменяемых типов, чтобы избежать неприятных ошибок во время выполнения.
Контравариантность внутри
Контравариантность противоположна Ковариации . Контравариантность позволяет нам использовать Супертип вместо подтипа .
Рассмотрим следующий класс Инвентаризация . Класс инвентаря принимает товар типа T .
class Inventory(item: T)
У нас есть Магазин в котором хранится Инвентаризация .
class Shop(items: Inventory )
Учтите, что у нас есть два магазина: Магазин для животных и Магазин для собак. Затем по какой-то причине магазин животных решает продать всех своих собак в магазин собак.
Но собаки в Зоомагазине все еще помечены внутри Инвентарь<Животное> .
Если мы захотим Магазин<Собака> для приема всех Животных из из
fun main() {
val animal = Animal()
val dog = Dog()
val animalList = Inventory(a)
val dogShop = Shop(animalList) // 💥 Type error
}
из Магазина<Животное> , тогда , но мы пытаемся использовать
из Магазина<Животное> , тогда , но мы пытаемся использовать Супертип вместо
class Inventory(item: T)
из Магазина<Животное> , тогда , но мы пытаемся использовать Супертип вместо подтипа , компилятор выдает ошибку типа. Мы можем дать указание компилятору принять
|||| из || Магазина<Животное> || , тогда , но мы пытаемся использовать || Супертип || вместо || подтипа || , компилятор выдает ошибку типа. Мы можем дать указание компилятору принять || Супертип || вместо || подтипа || с ключевым словом || в || в определении типа. Об этом сообщит компилятор (т.е. || Животное || вместо || Собаки || ). для использования супертипа |||| Хорошая цитата, которая может помочь запомнить эти правила: будьте либеральны в том, что вы принимаете, и консервативны в том, что вы производите. на месте || подтипа ||
|||| из || Магазина<Животное> || , тогда , но мы пытаемся использовать || Супертип || вместо || подтипа || , компилятор выдает ошибку типа. Мы можем дать указание компилятору принять || Супертип || вместо || подтипа || с ключевым словом || в || в определении типа. Об этом сообщит компилятор (т.е. || Животное || вместо || Собаки || ). для использования супертипа |||| Хорошая цитата, которая может помочь запомнить эти правила: будьте либеральны в том, что вы принимаете, и консервативны в том, что вы производите. на месте || подтипа ||
Тип , который мы передаем , может быть либо подтипом , Супертип .
Существуют следующие типы дисперсии типов в целом:
- Ковариантный – это позволяет использовать
Супертипвместоподтипа. - Контравариантный – это позволяет использовать
ПодтипвместосуперТипа. - Двумерный – ковариантный и контравариантный.
- Инвариантный – ни ковариантный, ни контравариантный.
Ознакомьтесь с этим сообщением о том, как создать приложение с полным стеком с помощью Kotlin, React и Spring Boot с помощью Hipster здесь.
Интересно, что такое хипстер – посмотрите здесь .
Вы можете следовать за мной по Твиттер .
Если вам понравилась эта статья, пожалуйста, оставьте лайк или комментарий. ❤ ️
Заинтересованы в дальнейшем изучении:
Оригинал: “https://dev.to/sendilkumarn/kotlin-generics-type-variance-9ee”