← Назад к вопросам

Зачем нужны Generics?

2.3 Middle🔥 141 комментариев
#Kotlin основы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Generics в Kotlin/Java — мощный инструмент для type-safety

Generics — это механизм, который позволяет писать переиспользуемый, типобезопасный код для работы с разными типами данных без потери типизации.

Проблема без Generics

class Container {
    private var value: Any? = null
    
    fun set(v: Any?) {
        value = v
    }
    
    fun get(): Any? = value
}

// Проблемы:
val container = Container()
container.set("Hello")
val result = container.get() // Any? — нужно приводить тип
val str = result as String   // Небезопасное приведение!
val error = result as Int    // Может быть ClassCastException в runtime

Решение с Generics

class Container<T> {
    private var value: T? = null
    
    fun set(v: T) {
        value = v
    }
    
    fun get(): T? = value
}

// Правильное использование:
val stringContainer = Container<String>()
stringContainer.set("Hello")
val str = stringContainer.get()  // String?, компилятор знает тип!

val intContainer = Container<Int>()
intContainer.set(42)
val num = intContainer.get()  // Int?

// stringContainer.set(42)  // ОШИБКА КОМПИЛЯЦИИ — тип не совпадает!

Основные преимущества

Type Safety — ошибки ловятся на compile time, а не runtime Переиспользование — один код работает с разными типами Читаемость — явно видно с какими типами работает код Производительность — нет нужды в type casting

Примеры в Android разработке

1. Repository паттерн

interface Repository<T> {
    suspend fun getAll(): List<T>
    suspend fun getById(id: String): T?
    suspend fun save(item: T)
    suspend fun delete(item: T)
}

class UserRepository(private val api: UserApi) : Repository<User> {
    override suspend fun getAll(): List<User> = api.getUsers()
    override suspend fun getById(id: String): User? = api.getUser(id)
    override suspend fun save(item: User) = api.saveUser(item)
    override suspend fun delete(item: User) = api.deleteUser(item.id)
}

class ProductRepository(private val db: ProductDao) : Repository<Product> {
    override suspend fun getAll(): List<Product> = db.getAll()
    override suspend fun getById(id: String): Product? = db.getById(id)
    override suspend fun save(item: Product) = db.insert(item)
    override suspend fun delete(item: Product) = db.delete(item)
}

2. Result тип (часто используется)

seal class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

fun handleResult() {
    val userResult: Result<User> = fetchUser()
    when (userResult) {
        is Result.Success -> println(userResult.data.name)
        is Result.Error -> println(userResult.exception.message)
        is Result.Loading -> println("Loading...")
    }
}

3. StateFlow и LiveData

class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // Типизирован на User
    private val _user = MutableStateFlow<User?>(null)
    val user: StateFlow<User?> = _user.asStateFlow()
    
    // Типизирован на List<User>
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()
}

4. Extension функции с Generics

inline fun <T> T.let(block: (T) -> Unit): T {
    block(this)
    return this
}

val user = User("John", 25)
    .let { println("Created: $it") }
    .let { it.name }

// Практичный пример
inline fun <T> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
    if (this is Result.Success) {
        action(data)
    }
    return this
}

fetchUser()
    .onSuccess { user -> updateUI(user) }
    .onSuccess { user -> saveToCache(user) }

Bounded Type Parameters

// T должен быть Number
fun <T : Number> sum(a: T, b: T): Double {
    return a.toDouble() + b.toDouble()
}

sum(5, 10)      // OK — Int extends Number
sum(3.14, 2.86) // OK — Double extends Number
// sum("a", "b") // ОШИБКА — String не Number

// Несколько ограничений
fun <T> process(item: T) where T : Comparable<T>, T : Serializable {
    // item должен быть Comparable и Serializable одновременно
}

Variance — Ковариантность и Контравариантность

// Ковариантность (out) — только возвращаем T
class Producer<out T> {
    fun produce(): T = ???
    // fun consume(item: T) {} // НЕЛЬЗЯ!
}

val producer: Producer<Any> = Producer<String>()  // OK

// Контравариантность (in) — только принимаем T
class Consumer<in T> {
    fun consume(item: T) { }
    // fun produce(): T = ??? // НЕЛЬЗЯ!
}

val consumer: Consumer<String> = Consumer<Any>()  // OK

Generic DAO для Database

abstract class BaseDao<T> {
    @Insert
    abstract suspend fun insert(entity: T)
    
    @Update
    abstract suspend fun update(entity: T)
    
    @Delete
    abstract suspend fun delete(entity: T)
}

@Dao
interface UserDao : BaseDao<User> {
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getUserById(id: String): User?
}

@Dao
interface ProductDao : BaseDao<Product> {
    @Query("SELECT * FROM products WHERE category = :category")
    suspend fun getByCategory(category: String): List<Product>
}

Type Erasure — важная концепция

// В runtime generics информация стирается
val list: List<String> = listOf("a", "b")
list is List<String>  // true
list is List<Int>     // true (оба true!) — Type Erasure

// Решение: inline reified
inline fun <reified T> safeCast(obj: Any): T? = obj as? T

val result = safeCast<String>("hello")  // Работает корректно
val result2 = safeCast<String>(123)       // null (безопасно)

Когда использовать Generics

  • Контейнеры и коллекции (List<T>, Set<T>, Map<K, V>)
  • API классы (Repository<T>, Result<T>, LiveData<T>)
  • Переиспользуемые утилиты и расширения
  • Когда логика универсальна для всех типов

Когда НЕ использовать

  • Если тип фиксирован — используй конкретный тип
  • Не усложняй иерархию слишком вложенными generics
  • Избегай raw types (без <> скобок)

Итог

Generics — это фундаментальный инструмент modern Java/Kotlin разработки. Они обеспечивают:

  • Type Safety на compile time
  • Переиспользование кода без потери типизации
  • Лучшую архитектуру через полиморфизм
  • Профессиональный код который легче поддерживать

Мастерство Generics показывает глубокое понимание языка и способность писать правильные абстракции.

Зачем нужны Generics? | PrepBro