← Назад к вопросам
Как обязать Generic наследоваться от интерфейса
1.8 Middle🔥 151 комментариев
#Kotlin основы#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как обязать Generic наследоваться от интерфейса
Это делается через bounded type parameters (ограничены типы параметров). Синтаксис очень простой, но мощный.
1. Базовый синтаксис
Требование: Generic тип ДОЛЖЕН наследоваться от определённого интерфейса или класса.
// Синтаксис
fun <T : SomeInterface> function(param: T) { ... }
class MyClass<T : SomeInterface> { ... }
interface MyInterface<T : SomeInterface> { ... }
Пример:
// Generic T ДОЛЖЕН быть Comparable
fun <T : Comparable<T>> sort(list: List<T>) {
// Можно использовать методы Comparable
list.forEach { item ->
item.compareTo(item) // Этот метод есть! (гарантировано)
}
}
// Использование
sort(listOf(1, 2, 3)) // OK (Int имеет Comparable)
sort(listOf("a", "b")) // OK (String имеет Comparable)
sort(listOf(MyClass())) // ❌ Ошибка! MyClass не implements Comparable
2. Практический пример: Filterable
interface Filterable {
fun matches(query: String): Boolean
}
// T ДОЛЖЕН быть Filterable
fun <T : Filterable> filterItems(items: List<T>, query: String): List<T> {
return items.filter { it.matches(query) } // matches() гарантирован
}
// Классы для фильтрации
data class User(val name: String) : Filterable {
override fun matches(query: String): Boolean {
return name.contains(query, ignoreCase = true)
}
}
data class Product(val title: String) : Filterable {
override fun matches(query: String): Boolean {
return title.contains(query, ignoreCase = true)
}
}
// Использование
val users = listOf(
User("John"),
User("Jane"),
User("Bob")
)
val filtered = filterItems(users, "john") // OK
println(filtered) // [User("John")]
3. Множественные ограничения (Upper Bounds)
Можно требовать несколько интерфейсов одновременно:
// T ДОЛЖЕН быть Comparable И Serializable
fun <T> compare(a: T, b: T) where T : Comparable<T>, T : Serializable {
// Можно использовать методы обоих интерфейсов
a.compareTo(b) // Из Comparable
(a as? Serializable)?.let { it } // Из Serializable
}
// Альтернативный синтаксис (в Kotlin немного другой):
fun <T : Comparable<T>> compare(a: T, b: T) where T : Serializable {
// То же самое
}
4. Пример: Repository с ограничениями
// Интерфейс для сущностей с ID
interface Entity {
val id: Int
}
// Repository, который работает только с Entity
class Repository<T : Entity> {
private val cache = mutableMapOf<Int, T>()
fun save(item: T) {
cache[item.id] = item // item.id гарантирован!
}
fun getById(id: Int): T? {
return cache[id]
}
}
// Сущности
data class User(override val id: Int, val name: String) : Entity
data class Post(override val id: Int, val title: String) : Entity
// Использование
val userRepo = Repository<User>()
userRepo.save(User(1, "John")) // OK
val postRepo = Repository<Post>()
postRepo.save(Post(1, "Hello")) // OK
// ❌ Неправильно
data class InvalidClass(val data: String)
val badRepo = Repository<InvalidClass>() // Ошибка компиляции!
5. Ограничение классом
Можно требовать наследование от класса (не только интерфейса):
open class BaseModel {
abstract fun toJson(): String
}
// T ДОЛЖЕН наследоваться от BaseModel
fun <T : BaseModel> serialize(item: T): String {
return item.toJson() // Метод гарантирован
}
class UserModel : BaseModel() {
override fun toJson(): String = "{name: 'John'}"
}
serialize(UserModel()) // OK
6. Reified Type Parameters (специальный случай)
В Kotlin есть особенность reified для работы с типами в runtime:
inline fun <reified T> fromJson(json: String): T {
return Gson().fromJson(json, T::class.java) // Можно использовать T::class
}
data class User(val name: String)
val user = fromJson<User>("{\"name\":\"John\"}")
println(user) // User(name="John")
7. Пример: DAO с Type Bounds
interface Entity {
val id: Long
fun validate(): Boolean
}
// DAO только для Entity-типов
class BaseDao<T : Entity>(
private val database: AppDatabase
) {
fun insert(entity: T): Long {
if (!entity.validate()) {
throw IllegalArgumentException("Invalid entity")
}
// Вставляем в БД
return database.insert(entity)
}
fun update(entity: T) {
if (!entity.validate()) {
throw IllegalArgumentException("Invalid entity")
}
database.update(entity)
}
}
// Сущности
data class User(
override val id: Long = 0,
val name: String,
val email: String
) : Entity {
override fun validate(): Boolean {
return name.isNotEmpty() && email.contains("@")
}
}
data class Post(
override val id: Long = 0,
val title: String,
val content: String
) : Entity {
override fun validate(): Boolean {
return title.isNotEmpty() && content.isNotEmpty()
}
}
// Использование
val userDao = BaseDao<User>(database)
val newUser = User(name = "John", email = "john@example.com")
userDao.insert(newUser) // OK
val postDao = BaseDao<Post>(database)
val newPost = Post(title = "Hello", content = "World")
postDao.insert(newPost) // OK
8. Covariance и Bounds
// Covariant bound (producer)
class Producer<out T : Entity> {
fun produce(): T { /* ... */ }
}
// Contravariant bound (consumer)
class Consumer<in T : Entity> {
fun consume(item: T) { /* ... */ }
}
// Invariant (default)
class Container<T : Entity> {
fun get(): T { /* ... */ }
fun put(item: T) { /* ... */ }
}
9. Практическое применение: Adapter
interface ViewHolder {
fun bind(data: Any)
}
// Adapter для любых данных, которые имеют ViewHolder
class GenericAdapter<T : ViewHolder>(
private val items: List<T>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as? T)?.bind(items[position]) // T имеет метод bind()
}
override fun getItemCount(): Int = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// ...
return MyViewHolder()
}
}
class MyViewHolder : ViewHolder {
override fun bind(data: Any) {
// Привязываем данные
}
}
10. Ошибки при использовании Type Bounds
❌ Забыли ограничение
fun <T> process(item: T) {
item.validate() // ❌ Ошибка! T может не иметь validate()
}
// ✅ Правильно
fun <T : Entity> process(item: T) {
item.validate() // OK!
}
❌ Передали неправильный тип
fun <T : Entity> save(item: T) { /* ... */ }
data class WrongClass(val data: String)
save(WrongClass("test")) // ❌ Ошибка компиляции!
Итог
- Синтаксис:
<T : Interface>обязывает T иметь методы интерфейса - Множественные ограничения:
where T : Interface1, T : Interface2 - Проверяется на compile-time, так что ошибки ловятся рано
- Позволяет безопасно использовать методы Generic типа
- Широко используется в Repository, DAO, Adapter'ах
- Улучшает type safety и делает код понятнее