Ответ
В Kotlin можно ограничить Generic параметр интерфейсом с помощью where или :. Пример:
interface MyInterface {
fun doSomething()
}
class MyClass<T> where T : MyInterface {
fun process(item: T) {
item.doSomething()
}
}
// Или альтернативный синтаксис:
class AnotherClass<T : MyInterface>(val item: T)
Ключевые моменты:
- Ограничение гарантирует, что тип T реализует MyInterface
- Можно использовать несколько ограничений через
where T : A, T : B - Внутри класса доступны все методы интерфейса для типа T
Пример использования:
class Implementation : MyInterface {
override fun doSomething() = println("Working")
}
val obj = MyClass<Implementation>() Ответ 18+ 🔞
А, ну ты про дженерики в Kotlin! Давай разберём эту штуку, там всё не так страшно, как кажется на первый взгляд.
Представь, что ты пишешь какой-то универсальный ящик, класс, который должен работать с разными типами данных. Но не со всеми подряд, а только с теми, кто умеет делать определённые вещи. Вот тут и вступает в дело ограничение через интерфейс, чтобы всякие левые типы не пролезли.
Смотри, допустим, у тебя есть интерфейс — набор правил, контракт.
interface MyInterface {
fun doSomething()
}
Просто говорит: «Кто меня реализует, тот обязан иметь метод doSomething()». Ёпта, логично же.
А теперь ты хочешь создать класс, который будет принимать в работу только таких умельцев. Как сказать компилятору: «Слушай, пускай только тех, кто контракт подписал»?
Есть два основных способа, оба рабочие, выбирай какой больше нравится.
Способ первый — через where. Выглядит солидно, как будто ты условия ставишь.
class MyClass<T> where T : MyInterface {
fun process(item: T) {
item.doSomething() // Всё, тут уже можно спать спокойно. Компилятор знает, что у `item` этот метод точно есть.
}
}
Читается это как: «Класс MyClass работает с типом T, но только при условии, где T является наследником MyInterface». В рот меня чих-пых, звучит-то как! Прямо терпения ноль ебать ждать, когда какой-нибудь String попробует сюда пролезть — компилятор его нахуй пошлёт сразу.
Способ второй — короче, через двоеточие в объявлении класса. Для тех, кто не любит много букв.
class AnotherClass<T : MyInterface>(val item: T)
Тут всё то же самое, просто запись компактнее. «Класс AnotherClass для типа T, который должен быть MyInterface». И всё, да похуй на остальное.
А главная фишка в чём? Можно навесить овердохуища ограничений! Допустим, ты хочешь, чтобы твой тип T был не просто умным, а очень умным: и интерфейс A реализовывал, и от класса B наследовался.
class SuperClass<T> where T : MyInterface, T : SomeOtherClass {
// ...
}
Вот это уже мощно. Ты говоришь системе: «Пускать только избранных, которые прошли оба круга ада». Сам от себя охуел, какая гибкость.
Ну и пример использования, чтобы совсем уж ясно стало:
class Implementation : MyInterface {
override fun doSomething() = println("Working")
}
val obj = MyClass<Implementation>() // Всё ок, Implementation подписал контракт.
obj.process(Implementation()) // Напечатает "Working".
А если попробуешь MyClass<String>(), то получишь по шапке от компилятора — ни хуя себе, String-то наш метод doSomething() не реализует! Вот и весь сказ, чувак. Ограничиваешь — и спишь спокойно, никто тебе в коде сюрпризов гадких не подкинет.