Generics enable classes and functions to work with multiple data types while maintaining type safety. They provide better code reusability and type checking compared to simple inheritance hierarchies.
Upper Bounds
An upper bound restricts a type paramter to be a specific type or its subtypes. In Kotlin, use the colon syntax to specify an upper bound:
fun <T : Animal> processPet(pet: T) {
// T is guaranteed to be Animal or a subclass
pet.eat()
}
In Java, the equivalent syntax uses the extends keyword:
public <T extends Animal> void processPet(T pet) {
pet.eat();
}
Lower Bounds
Lower bounds restrict a type to be a supertype of the specified class. Kotlin uses the in keyword for this purpose:
fun <T in Animal> addToCollection(item: T) {
// item can be Animal or any supertype
}
Java uses the ? super T syntax for lower bounds.
Multiple Bounds
Kotlin supports multiple bounds using the where clause, allowing a type parameter to satisfy multiple constraints:
fun <T> executeAction(item: T) where T : Runnable, T : Serializable {
item.run() // From Runnable
// item can also be serialized
}
Java uses the & operator to combine multiple bounds:
public <T extends Runnable & Serializable> void executeAction(T item) {
item.run();
}
Enum Classes in Kotlin
Enum classes provide a type-safe way to represent a fixed set of related values. They are particularly useful when you need to ensure values are validated at compile time rather than runtime.
Consider a simple enum representing order states:
enum class OrderStatus {
Processing,
Shipped,
Delivered
}
fun processOrder(status: OrderStatus) {
when (status) {
OrderStatus.Processing -> handleProcessingOrder()
OrderStatus.Shipped -> handleShippedOrder()
OrderStatus.Delivered -> handleDeliveredOrder()
}
}
The compiler will warn you if you miss handling any enum case in a when expression, preventing runtime errors from unhandled states.
Enums with Properties
Enums can include properties and methods, making them suitable for binding to UI resources:
enum class UserRole(val resourceId: Int) {
Administrator(R.string.role_admin),
StandardUser(R.string.role_user),
Guest(R.string.role_guest)
}
fun displayRoleLabel(context: Context, role: UserRole): String {
return context.getString(role.resourceId)
}
Enum Classes vs String Resources
When deciding between enum classes and string resources, consider the following distinctions:
Use Enum Classes When:
- Values represent discrete states or categories in your domain logic
- Type safety is required to prevent invalid values
- You need to add behavior or properties associated with each value
- The compiler should enforce handling of all posible cases
Use String Resources When:
- Text is purely for display purposes and UI localization
- Values may change without code deployment
- Non-technical users need to modify text content
// Domain logic using enums for type safety
enum class PaymentMethod {
CreditCard,
PayPal,
BankTransfer
}
fun processPayment(method: PaymentMethod) {
when (method) {
PaymentMethod.CreditCard -> handleCardPayment()
PaymentMethod.PayPal -> handlePayPalPayment()
PaymentMethod.BankTransfer -> handleBankTransfer()
}
}
// UI display using string resources
fun getPaymentDisplayText(context: Context, method: PaymentMethod): String {
val stringRes = when (method) {
PaymentMethod.CreditCard -> R.string.payment_credit_card
PaymentMethod.PayPal -> R.string.payment_paypal
PaymentMethod.BankTransfer -> R.string.payment_bank_transfer
}
return context.getString(stringRes)
}
This separation keeps domain logic type-safe while allowing UI text to be localized and updated independently.