返回 Skill 列表
extension
分类: 开发与工程无需 API Key

lang-kotlin-dev

涵盖空安全、协程、数据类、扩展函数和Kotlin惯用法的基础Kotlin模式。在编写Kotlin代码或需要Kotlin开发指导时使用。这是Kotlin开发的入门点。

person作者: jakexiaohubgithub

Kotlin Development Fundamentals

Overview

Kotlin is a modern, statically typed language targeting JVM, JavaScript, and native platforms. It emphasizes:

  • Null safety at the type system level (nullable T? vs non-nullable T)
  • Concise syntax with data classes, smart casts, and type inference
  • Coroutines for structured asynchronous programming
  • Full Java interoperability for gradual migration

When to use this skill: Writing Kotlin applications, Android development, migrating from Java, or learning Kotlin idioms.

Skill Hierarchy

lang-kotlin-dev (foundational - THIS SKILL)
├── lang-kotlin-coroutines-eng (advanced concurrency patterns)
├── lang-kotlin-library-dev (library design and publishing)
├── lang-kotlin-patterns-eng (design patterns in Kotlin)
└── lang-kotlin-multiplatform-dev (KMP development)

Quick Reference

| Pattern | When to Use | Key Syntax | |---------|-------------|------------| | Null Safety | Handling nullable types | Type?, ?., !!, ?: | | Data Classes | Value objects, DTOs | data class User(val name: String) | | Sealed Classes | Restricted hierarchies | sealed class Result<T> | | Extension Functions | Add methods to existing types | fun String.isPalindrome() | | Scope Functions | Context-based operations | let, run, with, apply, also | | Coroutines | Asynchronous programming | suspend, launch, async | | Delegation | Property/class delegation | by lazy, by keyword | | Type-Safe Builders | DSL construction | Lambda with receiver |

Skill Routing

Route to specialized skills when:

  • lang-kotlin-coroutines-eng: Deep coroutine patterns (Flow, channels, structured concurrency)
  • lang-kotlin-library-dev: Building and publishing Kotlin libraries
  • lang-kotlin-patterns-eng: Advanced design patterns and architectural patterns
  • lang-kotlin-multiplatform-dev: Kotlin Multiplatform projects (KMP)

Module System

Kotlin organizes code into packages and provides visibility modifiers to control access.

1.1 Package Declarations

// Package declaration at file top
package com.example.myapp.utils

// Package-level functions (not in a class)
fun helper(): String = "I'm package-level"

// Package-level properties
val VERSION = "1.0.0"

1.2 Imports

// Single import
import com.example.myapp.User

// Wildcard import (all public members)
import com.example.myapp.utils.*

// Aliased import (resolve naming conflicts)
import com.example.myapp.User as AppUser
import org.external.User as ExternalUser

// Import extension functions
import com.example.extensions.formatCurrency

// Import enum entries
import com.example.Status.ACTIVE
import com.example.Status.INACTIVE

1.3 Visibility Modifiers

// public (default) - visible everywhere
class PublicClass

// internal - visible within the same module
internal class ModuleClass

// private - visible within the file (top-level) or class
private class FilePrivateClass

class Example {
    public val publicProp = 1      // Visible everywhere
    internal val internalProp = 2   // Same module
    protected val protectedProp = 3 // Subclasses only
    private val privateProp = 4     // This class only
}

1.4 Object Declarations (Singletons)

// Singleton object
object Logger {
    fun log(message: String) = println(message)
}

// Usage
Logger.log("Hello")

// Companion object (static-like members)
class Factory {
    companion object {
        fun create(): Factory = Factory()
        const val TAG = "Factory"
    }
}

// Usage
val instance = Factory.create()
val tag = Factory.TAG

1.5 File Organization

// Filename: UserRepository.kt
package com.example.repository

// Multiple classes per file allowed (unlike Java)
data class User(val id: Int, val name: String)
data class UserDto(val id: Int, val displayName: String)

// Extension functions in same file
fun User.toDto() = UserDto(id, name)

// Top-level functions
fun findUserById(id: Int): User? = null

Best Practices:

  • One primary class per file, named after the class
  • Group related extension functions with their target type
  • Use internal for module-private APIs in libraries

2. Null Safety

Kotlin's type system distinguishes between nullable and non-nullable types, eliminating most null pointer exceptions at compile time.

2.1 Nullable Types

// Non-nullable type (default)
var name: String = "Kotlin"
// name = null  // Compilation error

// Nullable type
var nullableName: String? = "Kotlin"
nullableName = null  // OK

// Function parameters
fun greet(name: String?) {
    // Must handle null case
}

2.2 Safe Call Operator (?.)

val length: Int? = nullableName?.length

// Chaining safe calls
val firstChar: Char? = nullableName?.uppercase()?.firstOrNull()

// Safe calls with let
nullableName?.let { name ->
    println("Name is $name")
}

// Safe call in chains
data class Person(val name: String, val address: Address?)
data class Address(val city: String?)

val city: String? = person?.address?.city

2.3 Elvis Operator (?:)

// Provide default value for null
val length: Int = nullableName?.length ?: 0

// Return early on null
fun processName(name: String?): String {
    val trimmed = name?.trim() ?: return "No name provided"
    return "Hello, $trimmed"
}

// Throw exception on null
val nonNullName = nullableName ?: throw IllegalArgumentException("Name required")

// Chaining with safe calls
val displayName = person?.name?.trim() ?: "Anonymous"

2.4 Not-Null Assertion (!!)

// Force unwrap - throws NPE if null
val length: Int = nullableName!!.length

// Use sparingly and only when certain
fun processSurelyNonNull(value: String?) {
    // Only if you're absolutely certain
    val definiteValue: String = value!!
}

// Better: Use assertion with reasoning
val config: Config = loadConfig()
    ?: error("Config must be initialized at startup")

Warning: Avoid !! in production code. Use proper null handling instead.

2.5 Safe Casts (as?)

// Safe cast returns null if cast fails
val stringValue: String? = value as? String

// Combined with elvis operator
val length: Int = (value as? String)?.length ?: 0

// Pattern matching alternative
when (value) {
    is String -> println("String of length ${value.length}")
    is Int -> println("Integer: $value")
    else -> println("Unknown type")
}

2.6 Null Safety Patterns

// Pattern 1: Early return with elvis
fun processUser(user: User?): Result {
    val validUser = user ?: return Result.Error("User not found")
    return Result.Success(validUser)
}

// Pattern 2: Smart casts after null check
fun printLength(value: String?) {
    if (value != null) {
        // value is smart-casted to String
        println(value.length)
    }
}

// Pattern 3: require/check for preconditions
fun processPositive(value: Int?) {
    requireNotNull(value) { "Value must not be null" }
    require(value > 0) { "Value must be positive" }
    // value is smart-casted to Int
}

// Pattern 4: Safe collection operations
val names: List<String?> = listOf("Alice", null, "Bob")
val nonNullNames: List<String> = names.filterNotNull()
val firstNonNull: String? = names.firstOrNull { it != null }

3. Data Classes

Data classes provide automatic implementation of equals(), hashCode(), toString(), and copy().

2.1 Basic Data Classes

// Simple data class
data class User(
    val id: Long,
    val name: String,
    val email: String
)

// Automatic implementations
val user1 = User(1, "Alice", "alice@example.com")
val user2 = User(1, "Alice", "alice@example.com")

println(user1 == user2)  // true (structural equality)
println(user1.toString())  // User(id=1, name=Alice, email=alice@example.com)

// Destructuring
val (id, name, email) = user1
println("User $name has ID $id")

2.2 Copy Method

data class User(
    val id: Long,
    val name: String,
    val email: String,
    val isActive: Boolean = true
)

val user = User(1, "Alice", "alice@example.com")

// Create modified copy
val updatedUser = user.copy(email = "alice@newdomain.com")
val inactiveUser = user.copy(isActive = false)

// Copy preserves other fields
println(updatedUser.name)  // Alice

2.3 Data Classes with Validation

data class Email(val value: String) {
    init {
        require(value.contains("@")) { "Invalid email format" }
    }
}

data class User(
    val id: Long,
    val name: String,
    val email: Email
) {
    init {
        require(id > 0) { "ID must be positive" }
        require(name.isNotBlank()) { "Name cannot be blank" }
    }
}

// Usage
val user = User(1, "Alice", Email("alice@example.com"))
// val invalid = User(-1, "", Email("invalid"))  // Throws IllegalArgumentException

2.4 Data Classes with Computed Properties

data class Rectangle(
    val width: Double,
    val height: Double
) {
    // Not part of equals/hashCode/toString
    val area: Double
        get() = width * height

    val perimeter: Double
        get() = 2 * (width + height)

    fun isSquare(): Boolean = width == height
}

val rect = Rectangle(4.0, 5.0)
println(rect.area)  // 20.0

2.5 Data Classes in Collections

data class User(val id: Long, val name: String)

// Set automatically uses equals/hashCode
val users = setOf(
    User(1, "Alice"),
    User(1, "Alice"),  // Duplicate, ignored
    User(2, "Bob")
)
println(users.size)  // 2

// Map keys
val userMap = mapOf(
    User(1, "Alice") to "admin",
    User(2, "Bob") to "user"
)

// Finding in collections
val alice = User(1, "Alice")
println(userMap[alice])  // admin

4. Sealed Classes and Interfaces

Sealed classes represent restricted hierarchies where all subclasses are known at compile time.

3.1 Basic Sealed Classes

// Sealed class for result types
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

// Exhaustive when expressions
fun <T> handleResult(result: Result<T>) {
    when (result) {
        is Result.Success -> println("Data: ${result.data}")
        is Result.Error -> println("Error: ${result.exception.message}")
        Result.Loading -> println("Loading...")
    }
    // No else branch needed - compiler knows all cases
}

3.2 Sealed Interfaces

sealed interface NetworkState

data class Connected(val speed: Int) : NetworkState
data class Disconnected(val reason: String) : NetworkState
data object Connecting : NetworkState

fun updateUI(state: NetworkState) {
    when (state) {
        is Connected -> showConnected(state.speed)
        is Disconnected -> showError(state.reason)
        Connecting -> showLoading()
    }
}

3.3 Nested Sealed Hierarchies

sealed class ApiResponse<out T> {
    data class Success<T>(val body: T) : ApiResponse<T>()

    sealed class Failure : ApiResponse<Nothing>() {
        data class HttpError(val code: Int, val message: String) : Failure()
        data class NetworkError(val exception: Exception) : Failure()
        data object Unauthorized : Failure()
    }

    data object Loading : ApiResponse<Nothing>()
}

fun <T> processResponse(response: ApiResponse<T>) {
    when (response) {
        is ApiResponse.Success -> handleSuccess(response.body)
        is ApiResponse.Failure.HttpError -> handleHttp(response.code)
        is ApiResponse.Failure.NetworkError -> handleNetwork(response.exception)
        ApiResponse.Failure.Unauthorized -> redirectToLogin()
        ApiResponse.Loading -> showSpinner()
    }
}

3.4 Sealed Classes for State Machines

sealed class OrderState {
    data object Created : OrderState()
    data class Confirmed(val confirmationId: String) : OrderState()
    data class Shipped(val trackingNumber: String) : OrderState()
    data class Delivered(val signature: String) : OrderState()
    data class Cancelled(val reason: String) : OrderState()
}

class Order(var state: OrderState) {
    fun confirm(confirmationId: String) {
        state = when (state) {
            is OrderState.Created -> OrderState.Confirmed(confirmationId)
            else -> throw IllegalStateException("Can only confirm created orders")
        }
    }

    fun ship(trackingNumber: String) {
        state = when (state) {
            is OrderState.Confirmed -> OrderState.Shipped(trackingNumber)
            else -> throw IllegalStateException("Can only ship confirmed orders")
        }
    }
}

3.5 Pattern: Railway-Oriented Programming

sealed class Either<out L, out R> {
    data class Left<L>(val value: L) : Either<L, Nothing>()
    data class Right<R>(val value: R) : Either<Nothing, R>()
}

// Extension functions for functional pipeline
fun <L, R, T> Either<L, R>.map(f: (R) -> T): Either<L, T> =
    when (this) {
        is Either.Left -> this
        is Either.Right -> Either.Right(f(value))
    }

fun <L, R, T> Either<L, R>.flatMap(f: (R) -> Either<L, T>): Either<L, T> =
    when (this) {
        is Either.Left -> this
        is Either.Right -> f(value)
    }

// Usage
fun validateEmail(email: String): Either<String, String> =
    if (email.contains("@")) Either.Right(email)
    else Either.Left("Invalid email")

fun validateLength(email: String): Either<String, String> =
    if (email.length >= 5) Either.Right(email)
    else Either.Left("Email too short")

val result = validateEmail("test@example.com")
    .flatMap { validateLength(it) }
    .map { it.lowercase() }

5. Extension Functions

Extension functions add new functions to existing classes without modifying their source code.

4.1 Basic Extensions

// Extend String
fun String.isPalindrome(): Boolean {
    val cleaned = this.lowercase().filter { it.isLetterOrDigit() }
    return cleaned == cleaned.reversed()
}

println("A man a plan a canal Panama".isPalindrome())  // true

// Extend nullable types
fun String?.orDefault(default: String): String = this ?: default

val name: String? = null
println(name.orDefault("Guest"))  // Guest

4.2 Extension Properties

// Read-only extension property
val String.wordCount: Int
    get() = split("\\s+".toRegex()).size

println("Hello world from Kotlin".wordCount)  // 4

// Extension property with backing field not allowed
// Must use get() or set()
var StringBuilder.lastChar: Char
    get() = this[length - 1]
    set(value) {
        this[length - 1] = value
    }

4.3 Generic Extensions

// Generic extension for any type
fun <T> T.print(): T {
    println(this)
    return this
}

"Hello".print().uppercase().print()

// Extension with type constraints
fun <T : Comparable<T>> List<T>.second(): T? =
    if (size >= 2) this[1] else null

// Extension for collections
fun <T> List<T>.secondOrNull(): T? =
    if (size >= 2) this[1] else null

println(listOf(1, 2, 3).secondOrNull())  // 2

4.4 Extensions on Companion Objects

class User(val name: String) {
    companion object {
        // Regular companion object function
    }
}

// Extension on companion object
fun User.Companion.fromJson(json: String): User {
    // Parse JSON and create User
    return User("Parsed from JSON")
}

// Usage
val user = User.fromJson("""{"name": "Alice"}""")

4.5 Scope-Limited Extensions

// Extension only visible in this scope
class HtmlBuilder {
    private fun String.escapeHtml(): String =
        replace("&", "&amp;")
            .replace("<", "&lt;")
            .replace(">", "&gt;")

    fun paragraph(text: String): String =
        "<p>${text.escapeHtml()}</p>"
}

// Extension not visible outside HtmlBuilder

4.6 Common Extension Patterns

// Collection extensions
fun <T> List<T>.splitAt(index: Int): Pair<List<T>, List<T>> =
    take(index) to drop(index)

fun <K, V> Map<K, V>.getOrThrow(key: K): V =
    this[key] ?: throw NoSuchElementException("Key $key not found")

// Validation extensions
fun String.requireNotBlank(fieldName: String): String {
    require(isNotBlank()) { "$fieldName cannot be blank" }
    return this
}

// Transformation extensions
fun String.toTitleCase(): String =
    split(" ").joinToString(" ") { word ->
        word.lowercase().replaceFirstChar { it.uppercase() }
    }

// Utility extensions
inline fun <T> T.applyIf(condition: Boolean, block: T.() -> Unit): T {
    if (condition) block()
    return this
}

val list = mutableListOf(1, 2, 3)
    .applyIf(someCondition) { add(4) }

6. Scope Functions

Scope functions execute a block of code within the context of an object.

5.1 let - Null Safety and Transformations

// Execute block if not null
val name: String? = "Alice"
name?.let {
    println("Name is $it")
    println("Length is ${it.length}")
}

// Transform and return result
val length = name?.let { it.length } ?: 0

// Chain operations
val result = getUserInput()
    ?.trim()
    ?.let { parseUserData(it) }
    ?.let { validateData(it) }
    ?.let { saveToDatabase(it) }

// Scope to new variable
val validatedEmail = email?.let { value ->
    if (value.contains("@")) value else null
}

5.2 run - Object Configuration and Computation

// Run on nullable receiver
val config: Config? = loadConfig()
val port = config?.run {
    validate()
    applyDefaults()
    port
} ?: 8080

// Compute with context
val hexColor = run {
    val r = (color shr 16) and 0xFF
    val g = (color shr 8) and 0xFF
    val b = color and 0xFF
    "#%02X%02X%02X".format(r, g, b)
}

// Scope local variables
val result = run {
    val a = computeA()
    val b = computeB()
    a + b
}

5.3 with - Multiple Operations on Same Object

// Non-extension function
val person = Person()
with(person) {
    name = "Alice"
    age = 30
    email = "alice@example.com"
    validate()
}

// Return value from with
val result = with(StringBuilder()) {
    append("Hello")
    append(" ")
    append("World")
    toString()
}

// Multiple property access
val info = with(user) {
    "Name: $name, Email: $email, Active: $isActive"
}

5.4 apply - Object Configuration (Returns Receiver)

// Builder-style configuration
val person = Person().apply {
    name = "Alice"
    age = 30
    email = "alice@example.com"
}

// Chain multiple apply blocks
val request = HttpRequest().apply {
    url = "https://api.example.com"
    method = "POST"
}.apply {
    addHeader("Content-Type", "application/json")
    addHeader("Authorization", "Bearer $token")
}.apply {
    body = """{"data": "value"}"""
}

// Conditional configuration
val user = User().apply {
    name = "Alice"
    if (isAdmin) {
        role = "ADMIN"
        permissions = adminPermissions
    }
}

5.5 also - Side Effects (Returns Receiver)

// Logging and debugging
val result = processData(input)
    .also { println("Processed result: $it") }
    .also { log.debug("Result details: $it") }

// Validation before return
fun createUser(name: String): User {
    return User(name).also {
        require(it.isValid()) { "Invalid user" }
        saveToDatabase(it)
    }
}

// Multiple side effects
val file = File("output.txt")
    .also { it.createNewFile() }
    .also { it.setWritable(true) }
    .also { it.writeText("Hello") }

5.6 Choosing the Right Scope Function

// Decision guide:

// let - Transform nullable, introduce local scope
val length: Int = nullableString?.let { it.length } ?: 0

// run - Compute value using multiple statements
val total = run {
    val subtotal = items.sumOf { it.price }
    val tax = subtotal * 0.1
    subtotal + tax
}

// with - Multiple operations, focus on context
with(canvas) {
    drawLine(0, 0, width, height)
    drawCircle(width / 2, height / 2, 50)
}

// apply - Configure object, return it
val person = Person().apply {
    name = "Alice"
    age = 30
}

// also - Side effects, return object
val saved = user.also { repository.save(it) }

5.7 Nested Scope Functions

// Chaining different scope functions
val result = fetchUser(id)
    ?.let { user ->
        user.apply {
            lastLoginAt = Instant.now()
        }
    }
    ?.also { repository.save(it) }
    ?.let { user ->
        UserDto(user.id, user.name)
    }

// Complex transformations
val config = loadConfig()
    ?.run {
        validate()
        this
    }
    ?.apply {
        applyDefaults()
    }
    ?.also { logger.info("Config loaded: $it") }
    ?: DefaultConfig()

7. Collections and Sequences

Kotlin provides rich collection APIs with eager (collections) and lazy (sequences) evaluation.

6.1 Collection Basics

// List (immutable)
val numbers = listOf(1, 2, 3, 4, 5)
val empty = emptyList<Int>()

// MutableList
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable.removeAt(0)

// Set (unique elements)
val uniqueNumbers = setOf(1, 2, 2, 3, 3)  // [1, 2, 3]
val mutableSet = mutableSetOf<String>()

// Map
val ages = mapOf("Alice" to 30, "Bob" to 25)
val mutableMap = mutableMapOf<String, Int>()
mutableMap["Charlie"] = 35

6.2 Collection Transformations

val numbers = listOf(1, 2, 3, 4, 5)

// map - Transform each element
val squared = numbers.map { it * it }  // [1, 4, 9, 16, 25]

// filter - Keep matching elements
val evens = numbers.filter { it % 2 == 0 }  // [2, 4]

// mapNotNull - Transform and filter nulls
val strings = listOf("1", "2", "abc", "3")
val parsed = strings.mapNotNull { it.toIntOrNull() }  // [1, 2, 3]

// flatMap - Transform and flatten
val nested = listOf(listOf(1, 2), listOf(3, 4))
val flattened = nested.flatMap { it }  // [1, 2, 3, 4]

// partition - Split into two lists
val (evens, odds) = numbers.partition { it % 2 == 0 }

// groupBy - Group into map
val byParity = numbers.groupBy { if (it % 2 == 0) "even" else "odd" }
// {odd=[1, 3, 5], even=[2, 4]}

6.3 Collection Aggregations

val numbers = listOf(1, 2, 3, 4, 5)

// sum, average, min, max
val sum = numbers.sum()  // 15
val avg = numbers.average()  // 3.0
val max = numbers.maxOrNull()  // 5

// sumOf - Transform then sum
val lengthSum = listOf("a", "bb", "ccc").sumOf { it.length }  // 6

// reduce - Combine elements
val product = numbers.reduce { acc, n -> acc * n }  // 120

// fold - Reduce with initial value
val sumPlus10 = numbers.fold(10) { acc, n -> acc + n }  // 25

// joinToString - Create string
val csv = numbers.joinToString(separator = ",")  // "1,2,3,4,5"
val custom = numbers.joinToString(
    separator = " | ",
    prefix = "[",
    postfix = "]",
    limit = 3,
    truncated = "..."
) { "Item $it" }
// [Item 1 | Item 2 | Item 3 | ...]

6.4 Collection Queries

val numbers = listOf(1, 2, 3, 4, 5)

// any, all, none
val hasEven = numbers.any { it % 2 == 0 }  // true
val allPositive = numbers.all { it > 0 }  // true
val noneNegative = numbers.none { it < 0 }  // true

// find, first, last
val firstEven = numbers.find { it % 2 == 0 }  // 2
val firstOrNull = numbers.firstOrNull { it > 10 }  // null
val last = numbers.last()  // 5

// take, drop, slice
val first3 = numbers.take(3)  // [1, 2, 3]
val without2 = numbers.drop(2)  // [3, 4, 5]
val middle = numbers.slice(1..3)  // [2, 3, 4]

// distinct, distinctBy
val duplicates = listOf(1, 2, 2, 3, 3, 3)
val unique = duplicates.distinct()  // [1, 2, 3]

data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 30), Person("Bob", 30))
val byAge = people.distinctBy { it.age }  // [Person("Alice", 30)]

6.5 Sequences (Lazy Evaluation)

// Sequence - lazy evaluation
val sequence = sequenceOf(1, 2, 3, 4, 5)

// Convert to sequence for lazy evaluation
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// Eager (collection) - all operations execute immediately
val eagerResult = numbers
    .map { println("map $it"); it * 2 }  // Prints 10 times
    .filter { println("filter $it"); it > 10 }  // Prints 10 times
    .take(2)  // Still processed all elements

// Lazy (sequence) - operations execute on demand
val lazyResult = numbers.asSequence()
    .map { println("map $it"); it * 2 }
    .filter { println("filter $it"); it > 10 }
    .take(2)  // Only processes until 2 elements found
    .toList()

// Generate infinite sequence
val naturalNumbers = generateSequence(1) { it + 1 }
val first100Even = naturalNumbers
    .filter { it % 2 == 0 }
    .take(100)
    .toList()

// Sequence from function
val fibonacci = sequence {
    var a = 0
    var b = 1
    while (true) {
        yield(a)
        val next = a + b
        a = b
        b = next
    }
}

val first10Fib = fibonacci.take(10).toList()

6.6 Collection Building

// buildList - Builder function
val squares = buildList {
    for (i in 1..5) {
        add(i * i)
    }
    if (someCondition) {
        add(100)
    }
}

// buildSet
val uniqueWords = buildSet {
    add("hello")
    add("world")
    add("hello")  // Duplicate ignored
}

// buildMap
val characterCounts = buildMap {
    for (char in "hello") {
        put(char, getOrDefault(char, 0) + 1)
    }
}

// associate - Build map from collection
val users = listOf(User(1, "Alice"), User(2, "Bob"))
val byId = users.associateBy { it.id }
val byName = users.associateBy({ it.name }, { it.id })

6.7 Advanced Collection Patterns

// Windowed - Sliding windows
val numbers = listOf(1, 2, 3, 4, 5)
val windows = numbers.windowed(size = 3, step = 1)
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

// Chunked - Fixed-size groups
val chunks = numbers.chunked(2)  // [[1, 2], [3, 4], [5]]

// Zip - Combine two collections
val names = listOf("Alice", "Bob")
val ages = listOf(30, 25)
val pairs = names.zip(ages)  // [(Alice, 30), (Bob, 25)]
val combined = names.zip(ages) { name, age -> "$name is $age" }

// Union, intersect, subtract (Sets)
val set1 = setOf(1, 2, 3)
val set2 = setOf(2, 3, 4)
val union = set1 union set2  // [1, 2, 3, 4]
val intersect = set1 intersect set2  // [2, 3]
val subtract = set1 subtract set2  // [1]

8. Higher-Order Functions and Lambdas

Functions that take functions as parameters or return functions.

7.1 Lambda Syntax

// Lambda expression
val sum: (Int, Int) -> Int = { a, b -> a + b }
println(sum(2, 3))  // 5

// Single parameter - implicit 'it'
val square: (Int) -> Int = { it * it }
println(square(5))  // 25

// No parameters
val greeting: () -> String = { "Hello" }
println(greeting())  // Hello

// Multiple statements
val process: (Int) -> Int = { value ->
    val doubled = value * 2
    val squared = doubled * doubled
    squared  // Last expression is return value
}

7.2 Function Types

// Function type notation
val operation: (Int, Int) -> Int = { a, b -> a + b }

// Nullable function type
val nullableOp: ((Int, Int) -> Int)? = null

// Function type with receiver
val stringBuilder: StringBuilder.() -> Unit = {
    append("Hello")
    append(" World")
}

// Higher-order function
fun calculate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
    return op(a, b)
}

println(calculate(5, 3, { a, b -> a + b }))  // 8
println(calculate(5, 3) { a, b -> a * b })  // 15 (trailing lambda)

7.3 Trailing Lambda Syntax

// When last parameter is function, move outside parentheses
val numbers = listOf(1, 2, 3, 4, 5)

// Standard syntax
numbers.filter({ it % 2 == 0 })

// Trailing lambda
numbers.filter { it % 2 == 0 }

// Only lambda parameter - omit parentheses
numbers.forEach { println(it) }

// Multiple parameters with trailing lambda
numbers.fold(0) { acc, n -> acc + n }

7.4 Function References

// Top-level function reference
fun isEven(n: Int): Boolean = n % 2 == 0

val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter(::isEven)

// Member function reference
class StringUtils {
    fun isPalindrome(s: String): Boolean {
        return s == s.reversed()
    }
}

val utils = StringUtils()
val words = listOf("level", "hello", "radar")
val palindromes = words.filter(utils::isPalindrome)

// Constructor reference
data class Person(val name: String, val age: Int)
val names = listOf("Alice", "Bob")
val people = names.map(::Person)  // Partial application not supported

7.5 Inline Functions

// Inline function - lambda code inlined at call site
inline fun <T> measureTime(block: () -> T): T {
    val start = System.currentTimeMillis()
    val result = block()
    val end = System.currentTimeMillis()
    println("Took ${end - start}ms")
    return result
}

// Usage - no lambda object created
val result = measureTime {
    Thread.sleep(100)
    42
}

// noinline - Don't inline specific lambda
inline fun complexOperation(
    inline operation: () -> Unit,
    noinline callback: () -> Unit
) {
    operation()
    storeCallback(callback)  // Can store noinline lambda
}

// crossinline - Lambda can't use non-local returns
inline fun runAsync(crossinline block: () -> Unit) {
    thread {
        block()  // Would fail without crossinline if block has return
    }
}

7.6 Returning Functions

// Function that returns function
fun makeMultiplier(factor: Int): (Int) -> Int {
    return { value -> value * factor }
}

val double = makeMultiplier(2)
val triple = makeMultiplier(3)

println(double(5))  // 10
println(triple(5))  // 15

// Closure captures variables
fun makeCounter(): () -> Int {
    var count = 0
    return { ++count }
}

val counter = makeCounter()
println(counter())  // 1
println(counter())  // 2
println(counter())  // 3

7.7 Common Higher-Order Function Patterns

// Retry pattern
inline fun <T> retry(
    times: Int = 3,
    delay: Long = 1000,
    block: () -> T
): T {
    repeat(times - 1) {
        try {
            return block()
        } catch (e: Exception) {
            Thread.sleep(delay)
        }
    }
    return block()  // Last attempt throws if fails
}

// Resource management
inline fun <T : AutoCloseable, R> T.use(block: (T) -> R): R {
    try {
        return block(this)
    } finally {
        close()
    }
}

// Memoization
fun <P, R> memoize(fn: (P) -> R): (P) -> R {
    val cache = mutableMapOf<P, R>()
    return { param ->
        cache.getOrPut(param) { fn(param) }
    }
}

val fibonacci = memoize<Int, Long> { n ->
    if (n <= 1) n.toLong()
    else fibonacci(n - 1) + fibonacci(n - 2)
}

9. Coroutines Basics

Kotlin coroutines provide asynchronous programming support with sequential-looking code.

8.1 Suspend Functions

import kotlinx.coroutines.*

// Suspend function - can only be called from coroutine or another suspend function
suspend fun fetchUser(id: Long): User {
    delay(1000)  // Non-blocking delay
    return User(id, "Alice")
}

suspend fun fetchPosts(userId: Long): List<Post> {
    delay(500)
    return listOf(Post(1, "Hello"), Post(2, "World"))
}

// Calling suspend functions
suspend fun loadUserData(id: Long): UserData {
    val user = fetchUser(id)  // Sequential execution
    val posts = fetchPosts(user.id)
    return UserData(user, posts)
}

8.2 Coroutine Builders

import kotlinx.coroutines.*

// launch - Fire and forget
fun main() = runBlocking {
    launch {
        delay(1000)
        println("World")
    }
    println("Hello")
    // Output: Hello, World (after 1 second)
}

// async - Return value
fun main() = runBlocking {
    val deferred = async {
        delay(1000)
        "Result"
    }
    println("Waiting...")
    val result = deferred.await()  // Suspend until result ready
    println(result)
}

// runBlocking - Bridge to coroutine world
fun main() {
    runBlocking {
        launch {
            delay(1000)
            println("Coroutine")
        }
        println("Main")
    }
    println("Done")
}

8.3 Concurrent Execution

suspend fun loadUserData(id: Long): UserData = coroutineScope {
    // Sequential (slow)
    val user = fetchUser(id)
    val posts = fetchPosts(id)

    // Concurrent (fast)
    val userDeferred = async { fetchUser(id) }
    val postsDeferred = async { fetchPosts(id) }

    UserData(
        user = userDeferred.await(),
        posts = postsDeferred.await()
    )
}

// Multiple concurrent operations
suspend fun loadDashboard(): Dashboard = coroutineScope {
    val user = async { fetchUser() }
    val notifications = async { fetchNotifications() }
    val messages = async { fetchMessages() }
    val stats = async { fetchStats() }

    Dashboard(
        user.await(),
        notifications.await(),
        messages.await(),
        stats.await()
    )
}

8.4 Coroutine Context and Dispatchers

import kotlinx.coroutines.*

// Dispatchers.Default - CPU-intensive work
launch(Dispatchers.Default) {
    val result = performHeavyComputation()
}

// Dispatchers.IO - I/O operations
launch(Dispatchers.IO) {
    val data = readFromDatabase()
}

// Dispatchers.Main - UI operations (Android/Desktop)
launch(Dispatchers.Main) {
    updateUI(data)
}

// Switch contexts
suspend fun fetchAndDisplay() {
    val data = withContext(Dispatchers.IO) {
        fetchFromNetwork()
    }
    withContext(Dispatchers.Main) {
        displayData(data)
    }
}

8.5 Structured Concurrency

// coroutineScope - Creates scope, suspends until all children complete
suspend fun processAll(items: List<Item>): List<Result> = coroutineScope {
    items.map { item ->
        async { processItem(item) }
    }.awaitAll()  // Waits for all to complete
}

// supervisorScope - Children failures don't cancel siblings
suspend fun fetchMultiple(): List<Data?> = supervisorScope {
    val results = listOf(
        async { fetchFromSource1() },
        async { fetchFromSource2() },  // If this fails...
        async { fetchFromSource3() }   // ...this still runs
    )

    results.map {
        try { it.await() }
        catch (e: Exception) { null }
    }
}

// Cancellation
val job = launch {
    repeat(1000) { i ->
        println("Job: $i")
        delay(500)
    }
}

delay(2000)
job.cancel()  // Cancel the job
job.join()    // Wait for cancellation to complete

8.6 Exception Handling in Coroutines

// try-catch in coroutines
suspend fun safeOperation(): Result<Data> {
    return try {
        val data = fetchData()
        Result.Success(data)
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught $exception")
}

val scope = CoroutineScope(Dispatchers.Default + handler)
scope.launch {
    throw Exception("Failed")
}

// supervisorScope with exception handling
suspend fun robustFetch(): List<Data?> = supervisorScope {
    val sources = listOf(source1, source2, source3)
    sources.map { source ->
        async {
            try {
                source.fetch()
            } catch (e: Exception) {
                logger.error("Failed to fetch from $source", e)
                null
            }
        }
    }.awaitAll()
}

8.7 Common Coroutine Patterns

// Timeout
suspend fun fetchWithTimeout(id: Long): User? {
    return try {
        withTimeout(5000) {  // 5 second timeout
            fetchUser(id)
        }
    } catch (e: TimeoutCancellationException) {
        null
    }
}

// Retry with delay
suspend fun fetchWithRetry(maxAttempts: Int = 3): Data {
    repeat(maxAttempts - 1) { attempt ->
        try {
            return fetchData()
        } catch (e: Exception) {
            delay(1000 * (attempt + 1))  // Exponential backoff
        }
    }
    return fetchData()  // Last attempt
}

// Parallel processing with limit
suspend fun processInParallel(
    items: List<Item>,
    concurrency: Int = 10
): List<Result> = coroutineScope {
    items.chunked(concurrency).flatMap { chunk ->
        chunk.map { item ->
            async { processItem(item) }
        }.awaitAll()
    }
}

10. Kotlin Idioms

Idiomatic Kotlin patterns that make code more concise and expressive.

9.1 String Templates

val name = "Alice"
val age = 30

// Simple interpolation
val greeting = "Hello, $name!"

// Expressions in templates
val info = "Name: $name, Age: $age, Adult: ${age >= 18}"

// Multi-line strings
val json = """
    {
        "name": "$name",
        "age": $age,
        "address": {
            "city": "New York"
        }
    }
""".trimIndent()

// Raw strings with custom margin
val sql = """
    |SELECT *
    |FROM users
    |WHERE name = '$name'
    |AND age > $age
""".trimMargin()

9.2 Destructuring

// Data classes
data class User(val id: Long, val name: String, val email: String)
val user = User(1, "Alice", "alice@example.com")
val (id, name, email) = user

// Pairs and Triples
val pair = "key" to "value"
val (key, value) = pair

// In loops
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
for ((key, value) in map) {
    println("$key -> $value")
}

// Component functions for custom types
class Range(val start: Int, val end: Int) {
    operator fun component1() = start
    operator fun component2() = end
}

val range = Range(1, 10)
val (start, end) = range

9.3 Smart Casts

// After type check, automatic cast
fun process(value: Any) {
    if (value is String) {
        // value is automatically String here
        println(value.length)
    }
}

// Smart cast with when
fun describe(obj: Any): String = when (obj) {
    is String -> "String of length ${obj.length}"
    is Int -> "Integer: $obj"
    is List<*> -> "List of size ${obj.size}"
    else -> "Unknown type"
}

// Smart cast after null check
fun printLength(str: String?) {
    if (str != null) {
        // str is automatically String (non-null)
        println(str.length)
    }
}

// Smart cast with require/check
fun process(value: Any) {
    require(value is String) { "Must be String" }
    // value is String here
    println(value.uppercase())
}

9.4 Named Arguments and Default Parameters

// Default parameters
fun createUser(
    name: String,
    age: Int = 18,
    email: String? = null,
    isActive: Boolean = true
) = User(name, age, email, isActive)

// Call with defaults
val user1 = createUser("Alice")
val user2 = createUser("Bob", age = 25)
val user3 = createUser("Charlie", email = "charlie@example.com", age = 30)

// Named arguments improve readability
val rect = Rectangle(
    width = 100.0,
    height = 50.0,
    color = Color.RED,
    borderWidth = 2.0
)

// Reordering with named arguments
fun formatDate(
    day: Int,
    month: Int,
    year: Int,
    separator: String = "-"
): String = "$year$separator$month$separator$day"

val date = formatDate(year = 2024, month = 3, day = 15)

9.5 Single Expression Functions

// Function with expression body
fun square(x: Int): Int = x * x

// Type inference
fun double(x: Int) = x * 2

// With when expression
fun sign(x: Int) = when {
    x > 0 -> "positive"
    x < 0 -> "negative"
    else -> "zero"
}

// With if expression
fun max(a: Int, b: Int) = if (a > b) a else b

// Chained calls
fun processName(name: String) = name
    .trim()
    .lowercase()
    .replaceFirstChar { it.uppercase() }

9.6 Operator Overloading

// Arithmetic operators
data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point) = Point(x + other.x, y + other.y)
    operator fun minus(other: Point) = Point(x - other.x, y - other.y)
    operator fun times(scale: Int) = Point(x * scale, y * scale)
}

val p1 = Point(1, 2)
val p2 = Point(3, 4)
val p3 = p1 + p2  // Point(4, 6)
val p4 = p1 * 2   // Point(2, 4)

// Comparison operators
data class Version(val major: Int, val minor: Int, val patch: Int) : Comparable<Version> {
    override fun compareTo(other: Version): Int {
        if (major != other.major) return major - other.major
        if (minor != other.minor) return minor - other.minor
        return patch - other.patch
    }
}

val v1 = Version(1, 2, 3)
val v2 = Version(1, 3, 0)
println(v1 < v2)  // true

// Index operators
class MutableGrid<T>(private val data: Array<Array<T>>) {
    operator fun get(row: Int, col: Int): T = data[row][col]
    operator fun set(row: Int, col: Int, value: T) {
        data[row][col] = value
    }
}

// Invoke operator
class Greeter(val greeting: String) {
    operator fun invoke(name: String) = "$greeting, $name!"
}

val greet = Greeter("Hello")
println(greet("Alice"))  // Hello, Alice!

9.7 Delegation

// Property delegation
class User {
    // Lazy initialization
    val expensiveData: Data by lazy {
        loadExpensiveData()
    }

    // Observable property
    var name: String by Delegates.observable("<no name>") { _, old, new ->
        println("Name changed from $old to $new")
    }

    // Veto changes
    var age: Int by Delegates.vetoable(0) { _, old, new ->
        new >= 0  // Only allow non-negative ages
    }
}

// Map delegation
class Config(map: Map<String, Any?>) {
    val host: String by map
    val port: Int by map
    val timeout: Long by map
}

val config = Config(mapOf(
    "host" to "localhost",
    "port" to 8080,
    "timeout" to 5000L
))

// Class delegation
interface Repository {
    fun save(data: Data)
    fun load(id: Long): Data
}

class CachingRepository(
    private val delegate: Repository,
    private val cache: Cache
) : Repository by delegate {
    // Override specific methods
    override fun load(id: Long): Data {
        return cache.get(id) ?: delegate.load(id).also {
            cache.put(id, it)
        }
    }
}

9.8 Type Aliases

// Type alias for complex types
typealias UserId = Long
typealias Predicate<T> = (T) -> Boolean
typealias StringMap = Map<String, String>

// Generic type alias
typealias ResultCallback<T> = (Result<T>) -> Unit

// Usage
fun findUser(id: UserId): User? = database.find(id)

fun filter(items: List<Int>, predicate: Predicate<Int>): List<Int> =
    items.filter(predicate)

val config: StringMap = mapOf("key" to "value")

// Type alias for function types
typealias ClickHandler = (View, Int) -> Unit

fun setClickListener(handler: ClickHandler) {
    // ...
}

11. Java Interoperability

Kotlin provides seamless interop with Java code.

10.1 Calling Java from Kotlin

// Java getter/setter accessed as properties
val list = ArrayList<String>()  // Java class
list.add("Kotlin")              // Java method
println(list.size)              // Java getSize() -> size property

// Java void methods
val file = File("test.txt")
file.createNewFile()  // Java void method

// Static methods
val sqrt = Math.sqrt(16.0)  // Java static method

// Object methods from Java
val stream = File("data.txt").inputStream()

10.2 Null Safety with Java

// Platform types (Type!) - nullable unknown
val javaString: String = getJavaString()  // May throw NPE if null

// Explicit nullable handling
val safeString: String? = getJavaString()
val length: Int = safeString?.length ?: 0

// Annotated Java code
// If Java uses @Nullable/@NotNull
val nullable: String? = getAnnotatedNullable()  // Kotlin knows it's nullable
val nonNull: String = getAnnotatedNonNull()     // Kotlin knows it's not null

10.3 Calling Kotlin from Java

// Kotlin code
class KotlinClass {
    // Becomes static method in Java
    companion object {
        @JvmStatic
        fun create(): KotlinClass = KotlinClass()
    }

    // Default parameters need @JvmOverloads
    @JvmOverloads
    fun greet(name: String, greeting: String = "Hello") =
        "$greeting, $name"
}

// Java usage
KotlinClass obj = KotlinClass.create();
String msg1 = obj.greet("Alice");
String msg2 = obj.greet("Bob", "Hi");

10.4 JVM Annotations

// @JvmName - Change JVM name
@file:JvmName("Utils")
package com.example

fun helper() { }
// Java: Utils.helper()

// @JvmStatic - Static method/field
object Singleton {
    @JvmStatic
    fun getInstance(): Singleton = this

    @JvmField
    val CONSTANT = 42
}

// @JvmOverloads - Generate overloads for default params
class User @JvmOverloads constructor(
    val name: String,
    val age: Int = 0,
    val email: String? = null
)
// Java can call: new User("Alice"), new User("Alice", 30), etc.

// @Throws - Declare checked exceptions
@Throws(IOException::class)
fun readFile(path: String): String {
    return File(path).readText()
}

10.5 SAM Conversions

// Java interface with single abstract method
// interface Runnable { void run(); }

// Kotlin lambda automatically converts
val runnable = Runnable { println("Running") }

// Java functional interfaces
val listener = ActionListener { event ->
    println("Action performed: $event")
}

// Explicit SAM constructor
val callback = Callback { result ->
    handleResult(result)
}

10.6 Handling Java Collections

// Java collections are mutable in Kotlin
val javaList: MutableList<String> = ArrayList()
javaList.add("Kotlin")

// Converting to Kotlin immutable
val kotlinList: List<String> = javaList.toList()

// Extension functions work on Java collections
val upperCased = javaList.map { it.uppercase() }

// Kotlin collection to Java
val kotlinSet = setOf(1, 2, 3)
val javaSet: java.util.Set<Int> = kotlinSet.toSet()

12. Best Practices

11.1 Prefer Immutability

// Use val instead of var
val name = "Alice"  // Preferred
var mutableName = "Bob"  // Use when mutation needed

// Immutable collections
val list = listOf(1, 2, 3)  // Preferred
val mutableList = mutableListOf(1, 2, 3)  // Use when mutation needed

// Immutable data classes
data class User(val id: Long, val name: String)

// Use copy() for changes
val user = User(1, "Alice")
val updated = user.copy(name = "Alicia")

11.2 Null Safety Best Practices

// Prefer non-null types
fun greet(name: String) {  // Preferred
    println("Hello, $name")
}

// Only use nullable when truly optional
fun findUser(id: Long): User?  // Returns null if not found

// Avoid !! operator
val user = getUser()!!  // Avoid - may throw NPE

// Better alternatives
val user = getUser() ?: return  // Early return
val user = getUser() ?: throw IllegalStateException("User required")
val name = getUser()?.name ?: "Unknown"

11.3 Scope Function Usage

// Use let for null safety and transformations
user?.let { u ->
    processUser(u)
    saveUser(u)
}

// Use apply for object configuration
val person = Person().apply {
    name = "Alice"
    age = 30
}

// Use also for side effects
val user = createUser().also {
    logger.info("Created user: ${it.id}")
    cache.put(it.id, it)
}

// Use run for complex computations
val result = run {
    val a = computeA()
    val b = computeB()
    combine(a, b)
}

// Use with for multiple operations on same object
with(canvas) {
    drawLine(0, 0, 100, 100)
    drawCircle(50, 50, 25)
}

11.4 Prefer Expressions Over Statements

// Use when expression
val result = when (status) {
    Status.SUCCESS -> handleSuccess()
    Status.ERROR -> handleError()
    Status.PENDING -> handlePending()
}

// Use if expression
val max = if (a > b) a else b

// Use try expression
val result = try {
    dangerousOperation()
} catch (e: Exception) {
    defaultValue
}

11.5 Leverage Type Inference

// Let compiler infer types when obvious
val name = "Alice"  // Type inferred as String
val numbers = listOf(1, 2, 3)  // List<Int>

// Specify type when it aids readability
val result: Result<User> = processUser()  // Clarity
val callback: (Int) -> Unit = { processValue(it) }  // Clarity

11.6 Use Extension Functions Wisely

// Good: Utility operations
fun String.isEmail(): Boolean = contains("@") && contains(".")

// Good: Domain-specific operations
fun User.isAdult(): Boolean = age >= 18

// Avoid: Extending too broadly
fun Any.doSomething() { }  // Too broad, avoid

// Avoid: Complex logic that should be in a class
// Long extension functions with many dependencies -> use class instead

13. Common Patterns

12.1 Builder Pattern

// Kotlin builder with DSL
class HttpRequest private constructor(
    val url: String,
    val method: String,
    val headers: Map<String, String>,
    val body: String?
) {
    class Builder {
        var url: String = ""
        var method: String = "GET"
        private val headers = mutableMapOf<String, String>()
        var body: String? = null

        fun header(key: String, value: String) = apply {
            headers[key] = value
        }

        fun build() = HttpRequest(url, method, headers, body)
    }
}

// Usage with apply
val request = HttpRequest.Builder().apply {
    url = "https://api.example.com"
    method = "POST"
    header("Content-Type", "application/json")
    body = """{"key": "value"}"""
}.build()

// Type-safe builder DSL
class Html {
    private val children = mutableListOf<Tag>()

    fun body(init: Body.() -> Unit) {
        children.add(Body().apply(init))
    }
}

class Body {
    fun p(text: String) {
        // Add paragraph
    }
}

// Usage
fun html(init: Html.() -> Unit): Html = Html().apply(init)

val page = html {
    body {
        p("Hello")
        p("World")
    }
}

12.2 Sealed Class for State

sealed class UiState<out T> {
    data object Idle : UiState<Nothing>()
    data object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val exception: Exception) : UiState<Nothing>()
}

// State machine
class ViewModel {
    var state: UiState<User> = UiState.Idle
        private set

    suspend fun loadUser(id: Long) {
        state = UiState.Loading
        state = try {
            val user = repository.fetchUser(id)
            UiState.Success(user)
        } catch (e: Exception) {
            UiState.Error(e)
        }
    }
}

// Rendering based on state
fun render(state: UiState<User>) {
    when (state) {
        UiState.Idle -> showEmpty()
        UiState.Loading -> showSpinner()
        is UiState.Success -> showUser(state.data)
        is UiState.Error -> showError(state.exception)
    }
}

12.3 Repository Pattern

// Repository interface
interface UserRepository {
    suspend fun getUser(id: Long): User?
    suspend fun saveUser(user: User)
    suspend fun deleteUser(id: Long)
}

// Implementation with caching
class CachedUserRepository(
    private val api: UserApi,
    private val cache: Cache
) : UserRepository {

    override suspend fun getUser(id: Long): User? {
        return cache.get(id) ?: api.fetchUser(id)?.also {
            cache.put(id, it)
        }
    }

    override suspend fun saveUser(user: User) {
        api.saveUser(user)
        cache.put(user.id, user)
    }

    override suspend fun deleteUser(id: Long) {
        api.deleteUser(id)
        cache.remove(id)
    }
}

12.4 Validation with Result

sealed class ValidationResult<out T> {
    data class Valid<T>(val value: T) : ValidationResult<T>()
    data class Invalid(val errors: List<String>) : ValidationResult<Nothing>()
}

fun validateUser(
    name: String,
    email: String,
    age: Int
): ValidationResult<User> {
    val errors = mutableListOf<String>()

    if (name.isBlank()) errors.add("Name cannot be blank")
    if (!email.contains("@")) errors.add("Invalid email")
    if (age < 0) errors.add("Age must be positive")

    return if (errors.isEmpty()) {
        ValidationResult.Valid(User(name, email, age))
    } else {
        ValidationResult.Invalid(errors)
    }
}

// Usage
when (val result = validateUser(name, email, age)) {
    is ValidationResult.Valid -> saveUser(result.value)
    is ValidationResult.Invalid -> showErrors(result.errors)
}

14. Troubleshooting

13.1 NullPointerException

// Problem: Calling !! on null
val name = getName()!!  // NPE if getName() returns null

// Solution: Use safe operators
val name = getName() ?: "default"
val length = getName()?.length ?: 0

// Solution: Early return
val name = getName() ?: return
processName(name)

13.2 Type Mismatch

// Problem: Platform type confusion
val javaString: String = getJavaString()  // May be null

// Solution: Use nullable type
val javaString: String? = getJavaString()

// Problem: Collection variance
val list: List<Any> = listOf<String>("a")  // OK - covariant
val mutableList: MutableList<Any> = mutableListOf<String>("a")  // Error - invariant

// Solution: Use read-only List or explicit type
val list: List<Any> = mutableListOf<String>("a")  // OK

13.3 Coroutine Cancellation

// Problem: Ignoring cancellation
suspend fun process() {
    while (true) {
        // Expensive operation - can't cancel
        processItem()
    }
}

// Solution: Check isActive or use cancellable functions
suspend fun process() = coroutineScope {
    while (isActive) {
        processItem()
    }
}

// Or use delay which is cancellable
suspend fun process() {
    while (true) {
        processItem()
        delay(100)  // Cancellation point
    }
}

13.4 Memory Leaks with Coroutines

// Problem: GlobalScope launches never cleaned
GlobalScope.launch {
    // This lives forever
}

// Solution: Use proper scope
class ViewModel : CoroutineScope {
    private val job = Job()
    override val coroutineContext = Dispatchers.Main + job

    fun loadData() {
        launch {  // Tied to ViewModel lifecycle
            // ...
        }
    }

    fun onCleared() {
        job.cancel()  // Clean up when done
    }
}

13.5 Lazy Initialization Issues

// Problem: Lazy not thread-safe by default (it is by default, but showing pattern)
val data by lazy { expensiveComputation() }

// For truly thread-unsafe scenarios
val data by lazy(LazyThreadSafetyMode.NONE) {
    expensiveComputation()  // Only if guaranteed single-threaded
}

// Problem: Lazy val in data class
data class User(val name: String) {
    val computed by lazy { expensiveOp() }  // Not in equals/hashCode
}

// Solution: Make it explicit
data class User(val name: String) {
    fun getComputed() = computedValue
    private val computedValue by lazy { expensiveOp() }
}

15. Testing

Kotlin's testing ecosystem provides powerful frameworks with idiomatic DSL support, extension functions, and coroutine testing utilities.

13.1 JUnit 5 with Kotlin

import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.*

class CalculatorTest {
    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        calculator = Calculator()
    }

    @Test
    fun `should add two numbers`() {
        val result = calculator.add(2, 3)
        assertEquals(5, result)
    }

    @Test
    fun `should throw on division by zero`() {
        assertThrows<ArithmeticException> {
            calculator.divide(10, 0)
        }
    }

    @Test
    @DisplayName("Addition is commutative")
    fun additionIsCommutative() {
        assertEquals(
            calculator.add(2, 3),
            calculator.add(3, 2)
        )
    }

    // Parameterized tests
    @ParameterizedTest
    @ValueSource(ints = [1, 2, 3, 4, 5])
    fun `should be positive`(number: Int) {
        assertTrue(number > 0)
    }

    @ParameterizedTest
    @CsvSource(
        "1, 2, 3",
        "10, 20, 30",
        "-1, 1, 0"
    )
    fun `should add correctly`(a: Int, b: Int, expected: Int) {
        assertEquals(expected, calculator.add(a, b))
    }
}

13.2 Kotest Framework

import io.kotest.core.spec.style.*
import io.kotest.matchers.*
import io.kotest.matchers.collections.*
import io.kotest.matchers.string.*

// BehaviorSpec - BDD style
class UserServiceSpec : BehaviorSpec({
    Given("a new user") {
        val user = User("Alice", "alice@example.com")

        When("we save the user") {
            val saved = userService.save(user)

            Then("the user should have an ID") {
                saved.id shouldNotBe null
            }

            Then("the user should be retrievable") {
                val found = userService.findById(saved.id!!)
                found shouldBe saved
            }
        }
    }
})

// FunSpec - simple function-based tests
class CalculatorSpec : FunSpec({
    test("addition should work") {
        val result = Calculator().add(2, 3)
        result shouldBe 5
    }

    test("division by zero should throw") {
        shouldThrow<ArithmeticException> {
            Calculator().divide(10, 0)
        }
    }

    context("with positive numbers") {
        test("multiplication should be positive") {
            Calculator().multiply(2, 3) shouldBe 6
        }
    }
})

// StringSpec - minimal style
class StringUtilsSpec : StringSpec({
    "length should return string length" {
        "hello".length shouldBe 5
    }

    "startsWith should check prefix" {
        "hello" should startWith("hel")
    }

    "uppercase should convert to upper" {
        "hello".uppercase() shouldBe "HELLO"
    }
})

// DescribeSpec - Jasmine/Mocha style
class ListSpec : DescribeSpec({
    describe("a list") {
        val list = listOf(1, 2, 3)

        it("should have size 3") {
            list.size shouldBe 3
        }

        it("should contain 2") {
            list shouldContain 2
        }

        describe("when filtered") {
            val filtered = list.filter { it > 1 }

            it("should have size 2") {
                filtered.size shouldBe 2
            }

            it("should only contain values > 1") {
                filtered shouldContainExactly listOf(2, 3)
            }
        }
    }
})

13.3 Kotest Matchers

import io.kotest.matchers.*
import io.kotest.matchers.collections.*
import io.kotest.matchers.string.*
import io.kotest.matchers.types.*

// Equality and comparison
value shouldBe expected
value shouldNotBe unexpected
value shouldBeGreaterThan 5
value shouldBeLessThanOrEqual 10

// Null checks
value.shouldNotBeNull()
value.shouldBeNull()

// Type checks
value shouldBeInstanceOf<String>()
value.shouldBeTypeOf<User>()

// String matchers
str shouldStartWith "prefix"
str shouldEndWith "suffix"
str shouldContain "middle"
str shouldMatch "\\d+".toRegex()
str.shouldBeEmpty()
str.shouldBeBlank()

// Collection matchers
list shouldContain element
list shouldContainAll listOf(1, 2, 3)
list shouldContainExactly listOf(1, 2, 3)
list shouldHaveSize 5
list.shouldBeEmpty()
list.shouldBeSorted()

// Map matchers
map shouldContainKey "key"
map shouldContainValue 42
map shouldContainAll mapOf("a" to 1, "b" to 2)

// Exception matchers
shouldThrow<IllegalArgumentException> {
    validate(null)
}

val exception = shouldThrow<CustomException> {
    dangerousOperation()
}
exception.message shouldBe "Expected error"

// Boolean matchers
condition.shouldBeTrue()
condition.shouldBeFalse()

// Custom matchers
data class User(val name: String, val age: Int)

fun beAdult() = object : Matcher<User> {
    override fun test(value: User) = MatcherResult(
        value.age >= 18,
        { "User ${value.name} should be adult but was ${value.age}" },
        { "User ${value.name} should not be adult" }
    )
}

user should beAdult()

13.4 MockK - Mocking Library

import io.mockk.*
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension

@ExtendWith(MockKExtension::class)
class UserServiceTest {
    @MockK
    private lateinit var repository: UserRepository

    @InjectMockKs
    private lateinit var service: UserService

    @Test
    fun `should find user by id`() {
        // Given
        val user = User(1, "Alice")
        every { repository.findById(1) } returns user

        // When
        val result = service.getUser(1)

        // Then
        result shouldBe user
        verify { repository.findById(1) }
    }

    @Test
    fun `should throw when user not found`() {
        // Given
        every { repository.findById(any()) } returns null

        // When/Then
        shouldThrow<UserNotFoundException> {
            service.getUser(999)
        }
    }

    @Test
    fun `should save user`() {
        // Given
        val user = User(0, "Bob")
        val slot = slot<User>()
        every { repository.save(capture(slot)) } answers { slot.captured.copy(id = 1) }

        // When
        val saved = service.createUser(user)

        // Then
        saved.id shouldBe 1
        saved.name shouldBe "Bob"
        verify(exactly = 1) { repository.save(any()) }
    }
}

// Relaxed mocks - return default values
val mock = mockk<UserRepository>(relaxed = true)

// Spy - partial mocking
val spy = spyk(RealUserService())
every { spy.generateId() } returns 42

// Mock extension functions
mockkStatic(String::toUpperCase)
every { "hello".toUpperCase() } returns "MOCKED"

// Mock objects
object Logger {
    fun log(message: String) = println(message)
}

mockkObject(Logger)
every { Logger.log(any()) } just Runs
verify { Logger.log("test") }

13.5 Coroutine Testing

import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.Test

class CoroutineServiceTest {

    @Test
    fun `should fetch user data`() = runTest {
        val service = UserService()

        val user = service.fetchUser(1)

        user shouldNotBe null
        user.name shouldBe "Alice"
    }

    @Test
    fun `should handle concurrent requests`() = runTest {
        val service = UserService()

        val users = (1..10).map { id ->
            async { service.fetchUser(id) }
        }.awaitAll()

        users.size shouldBe 10
        users.all { it != null }.shouldBeTrue()
    }

    @Test
    fun `should timeout on slow operations`() = runTest {
        val service = SlowService()

        shouldThrow<TimeoutCancellationException> {
            withTimeout(1000) {
                service.verySlowOperation()
            }
        }
    }

    @Test
    fun `should advance time virtually`() = runTest {
        val service = TimedService()

        // Virtual time - no actual delay
        delay(5000)
        val result = service.checkStatus()

        result shouldBe "Ready"
    }

    @Test
    fun `should test flow emissions`() = runTest {
        val flow = flowOf(1, 2, 3, 4, 5)

        val results = mutableListOf<Int>()
        flow.collect { results.add(it) }

        results shouldContainExactly listOf(1, 2, 3, 4, 5)
    }

    @Test
    fun `should test flow transformations`() = runTest {
        val flow = (1..5).asFlow()
            .map { it * 2 }
            .filter { it > 5 }

        val results = flow.toList()

        results shouldContainExactly listOf(6, 8, 10)
    }

    @Test
    fun `should use test dispatcher`() = runTest {
        val dispatcher = StandardTestDispatcher(testScheduler)
        val scope = CoroutineScope(dispatcher)

        var completed = false
        scope.launch {
            delay(1000)
            completed = true
        }

        // Not completed yet - virtual time hasn't advanced
        completed.shouldBeFalse()

        // Advance virtual time
        advanceTimeBy(1000)

        // Now completed
        completed.shouldBeTrue()
    }
}

// Testing suspending functions
class SuspendFunctionTest {

    @Test
    fun `should test suspend function`() = runTest {
        suspend fun fetchData(): String {
            delay(100)
            return "data"
        }

        val result = fetchData()
        result shouldBe "data"
    }

    @Test
    fun `should verify mock suspend calls`() = runTest {
        val repository = mockk<UserRepository>()
        coEvery { repository.fetchUser(any()) } returns User(1, "Alice")

        val user = repository.fetchUser(1)

        user.name shouldBe "Alice"
        coVerify { repository.fetchUser(1) }
    }
}

13.6 kotlin.test Assertions

import kotlin.test.*

class KotlinTestExample {

    @Test
    fun `should use kotlin test assertions`() {
        // Equality
        assertEquals(5, 2 + 3)
        assertNotEquals(4, 2 + 3)

        // Boolean
        assertTrue(2 + 2 == 4)
        assertFalse(2 + 2 == 5)

        // Null checks
        assertNull(null)
        assertNotNull("value")

        // Exceptions
        assertFails {
            throw IllegalStateException()
        }

        assertFailsWith<IllegalArgumentException> {
            require(false) { "Validation failed" }
        }

        // Content equality
        assertContentEquals(listOf(1, 2, 3), listOf(1, 2, 3))

        // Custom message
        assertEquals(5, 2 + 3, "Addition should work")
    }
}

13.7 Property-Based Testing with Kotest

import io.kotest.core.spec.style.StringSpec
import io.kotest.property.*
import io.kotest.property.arbitrary.*

class PropertyBasedTest : StringSpec({

    "string length is never negative" {
        checkAll<String> { str ->
            str.length shouldBeGreaterThanOrEqual 0
        }
    }

    "reversing a list twice returns original" {
        checkAll<List<Int>> { list ->
            list.reversed().reversed() shouldBe list
        }
    }

    "addition is commutative" {
        checkAll<Int, Int> { a, b ->
            a + b shouldBe b + a
        }
    }

    "custom generators" {
        val emails = arbitrary {
            val name = Arb.string(5..10, Codepoint.alphanumeric()).bind()
            val domain = Arb.string(5..10, Codepoint.alphanumeric()).bind()
            "$name@$domain.com"
        }

        checkAll(emails) { email ->
            email shouldContain "@"
            email shouldEndWith ".com"
        }
    }

    "edge cases" {
        val config = PropTestConfig(iterations = 1000)

        checkAll(config, Arb.int()) { n ->
            val doubled = n * 2
            if (n >= 0) {
                doubled shouldBeGreaterThanOrEqual n
            } else {
                doubled shouldBeLessThanOrEqual n
            }
        }
    }
})

13.8 Test DSL Patterns

// Custom test DSL
class TestDsl {
    fun scenario(name: String, block: ScenarioContext.() -> Unit) {
        println("Scenario: $name")
        ScenarioContext().block()
    }
}

class ScenarioContext {
    fun given(description: String, block: () -> Unit) {
        println("  Given $description")
        block()
    }

    fun whenever(description: String, block: () -> Unit) {
        println("  When $description")
        block()
    }

    fun then(description: String, block: () -> Unit) {
        println("  Then $description")
        block()
    }
}

// Usage
class UserFlowTest : StringSpec({
    "user registration flow" {
        scenario("new user signs up") {
            var user: User? = null

            given("valid user details") {
                user = User(name = "Alice", email = "alice@example.com")
            }

            whenever("user submits registration") {
                user = userService.register(user!!)
            }

            then("user should receive confirmation email") {
                emailService.wasSent(user!!.email).shouldBeTrue()
            }

            then("user should have active account") {
                user!!.isActive.shouldBeTrue()
            }
        }
    }
})

// Fixture DSL
fun testWithUser(block: suspend (User) -> Unit) = runTest {
    val user = createTestUser()
    try {
        block(user)
    } finally {
        cleanup(user)
    }
}

// Usage
@Test
fun `should update user profile`() = testWithUser { user ->
    val updated = service.updateProfile(user.id, "New Name")
    updated.name shouldBe "New Name"
}

// Data builders with DSL
fun user(block: UserBuilder.() -> Unit): User {
    return UserBuilder().apply(block).build()
}

class UserBuilder {
    var name: String = "Test User"
    var email: String = "test@example.com"
    var age: Int = 30
    var roles: List<String> = emptyList()

    fun build() = User(name, email, age, roles)
}

// Usage in tests
@Test
fun `should validate admin user`() {
    val admin = user {
        name = "Admin"
        email = "admin@example.com"
        roles = listOf("ADMIN", "USER")
    }

    admin.isAdmin().shouldBeTrue()
}

13.9 Integration Testing Patterns

import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
class DatabaseIntegrationTest {

    companion object {
        @Container
        val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply {
            withDatabaseName("testdb")
            withUsername("test")
            withPassword("test")
        }
    }

    @Test
    fun `should persist and retrieve user`() {
        val repository = UserRepository(postgres.jdbcUrl)
        val user = User(name = "Alice", email = "alice@example.com")

        val saved = repository.save(user)
        val found = repository.findById(saved.id)

        found shouldBe saved
    }
}

// HTTP client testing
@Test
fun `should call external API`() = runTest {
    val mockServer = MockWebServer()
    mockServer.enqueue(
        MockResponse()
            .setBody("""{"name": "Alice"}""")
            .setHeader("Content-Type", "application/json")
    )

    val client = HttpClient(mockServer.url("/"))
    val user = client.fetchUser(1)

    user.name shouldBe "Alice"
    mockServer.shutdown()
}

13.10 Test Organization Best Practices

// Nested tests for organization
@Nested
@DisplayName("User validation")
inner class UserValidation {

    @Test
    fun `should reject null name`() {
        shouldThrow<IllegalArgumentException> {
            User(name = null, email = "test@example.com")
        }
    }

    @Nested
    @DisplayName("Email validation")
    inner class EmailValidation {

        @Test
        fun `should reject invalid email format`() {
            shouldThrow<IllegalArgumentException> {
                User(name = "Alice", email = "invalid")
            }
        }

        @Test
        fun `should accept valid email`() {
            val user = User(name = "Alice", email = "alice@example.com")
            user.email shouldBe "alice@example.com"
        }
    }
}

// Tags for filtering tests
@Tag("integration")
@Tag("slow")
class SlowIntegrationTest {
    @Test
    fun `should complete long operation`() {
        // Long running test
    }
}

// Conditional test execution
@EnabledIf("customCondition")
fun customCondition(): Boolean = System.getenv("RUN_INTEGRATION") == "true"

@Test
fun `should run only in CI`() {
    // Only runs when condition is true
}

16. Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

  • patterns-concurrency-dev: Coroutines, Flow, channels, structured concurrency
  • patterns-serialization-dev: kotlinx.serialization, Jackson with Kotlin
  • patterns-metaprogramming-dev: Annotations, reflection, DSL builders
  • patterns-testing-dev: Test frameworks, mocking, property-based testing

17. Further Resources

Specialized Skills

  • lang-kotlin-coroutines-eng: Advanced Flow, channels, structured concurrency
  • lang-kotlin-library-dev: Publishing libraries, API design
  • lang-kotlin-patterns-eng: Design patterns and architectural patterns
  • lang-kotlin-multiplatform-dev: Kotlin Multiplatform (KMP)

Official Documentation

  • Kotlin Language Reference: https://kotlinlang.org/docs/reference/
  • Kotlin Coroutines Guide: https://kotlinlang.org/docs/coroutines-guide.html
  • Kotlin Standard Library: https://kotlinlang.org/api/latest/jvm/stdlib/

Style Guides

  • Kotlin Coding Conventions: https://kotlinlang.org/docs/coding-conventions.html
  • Android Kotlin Style Guide: https://developer.android.com/kotlin/style-guide