Kotlin’s type system combines safety and expressiveness. Here are the key constructs.
Data classes
Auto-generates equals, hashCode, toString, copy, and componentN functions.
data class User(val name: String, val age: Int)
val user = User("Alice", 30)
user.name // "Alice"
user.toString() // "User(name=Alice, age=30)"
user.copy(age = 31) // User(name=Alice, age=31)
// Destructuring
val (name, age) = user // name="Alice", age=30
// Data classes work in when expressions and collections
val set = setOf(User("Alice", 30)) // deduplication by equals/hashCodeSealed classes and interfaces
Restricted class hierarchies — the compiler knows all subtypes. Perfect for when exhaustiveness.
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
fun handle(result: Result<Int>) = when (result) {
is Result.Success -> println("Got ${result.value}")
is Result.Error -> println("Error: ${result.message}")
Result.Loading -> println("Loading...")
// no else needed — compiler knows all cases
}
// Sealed interfaces (1.5+)
sealed interface Node {
data class Leaf(val value: Int) : Node
data class Branch(val left: Node, val right: Node) : Node
}Enum classes
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
// With properties and methods
enum class Planet(val mass: Double, val radius: Double) {
EARTH(5.97e24, 6371.0),
MARS(6.42e23, 3389.5),
JUPITER(1.90e27, 69911.0);
fun surfaceGravity() = 6.674e-11 * mass / (radius * radius * 1000 * 1000)
}
// Using enums
val dir = Direction.NORTH
dir.name // "NORTH"
dir.ordinal // 0
Direction.valueOf("SOUTH") // Direction.SOUTH
Direction.entries // all entries
// In when expressions
fun describe(dir: Direction) = when (dir) {
Direction.NORTH -> "Up"
Direction.SOUTH -> "Down"
Direction.EAST -> "Right"
Direction.WEST -> "Left"
}Inline / value classes
Wrap a single value without allocation overhead. Useful for type safety.
@JvmInline
value class Password(val value: String) // no runtime allocation
@JvmInline
value class UserId(val id: Long) // type-safe at compile time, Long at runtime
fun findUser(id: UserId) { /* ... */ }
findUser(UserId(42L)) // ok
// findUser(42L) // compile error — wrong typeType aliases
typealias UserMap = Map<String, List<User>>
typealias Predicate<T> = (T) -> Boolean
val users: UserMap = mapOf("team1" to listOf(User("Alice", 30)))
val isAdult: Predicate<User> = { it.age >= 18 }Generics
// Generic class
class Box<T>(val value: T)
val intBox = Box(42) // Box<Int>
val stringBox = Box("hello") // Box<String>
// Generic function
fun <T> List<T>.second(): T = this[1]
// Variance
// Declaration-site: out = covariant (producer), in = contravariant (consumer)
interface Source<out T> { // T is covariant
fun next(): T
}
interface Sink<in T> { // T is contravariant
fun put(value: T)
}
// Use-site variance (like Java wildcards)
fun copy(from: Array<out Any>, to: Array<in Any>) {
from.forEachIndexed { i, v -> to[i] = v }
}
// Reified type parameters — available at runtime (inline only)
inline fun <reified T> isA(value: Any): Boolean = value is T
isA<String>("hello") // true
isA<Int>("hello") // falseObject expressions and declarations
// Singleton object
object Database {
val url = "jdbc:localhost:5432/mydb"
fun connect() { /* ... */ }
}
Database.connect()
// Companion object — like static in Java
class MyClass {
companion object {
fun create(): MyClass = MyClass()
const val TAG = "MyClass"
}
}
val instance = MyClass.create()
// Anonymous object (like Java anonymous inner class)
val listener = object : ClickListener {
override fun onClick() { /* ... */ }
}Extension functions
// Add methods to existing types
fun String.isEmail(): Boolean = this.contains("@") && this.contains(".")
fun Int.square(): Int = this * this
// Use
"[email protected]".isEmail() // true
5.square() // 25
// Extension on nullable
fun String?.isNullOrEmpty(): Boolean = this == null || this.isEmpty()
// Generic extension
fun <T> List<T>.secondOrNull(): T? =
if (size >= 2) this[1] else null