Open-source libraries used

1 libraries are bundled into this tool's code.

Kotlin Cheatsheet — Quick Reference

A Kotlin 2.0 syntax, null safety, coroutines, and most-used standard library cheatsheet covering about 80% of everyday scenarios.

Kt

Kotlin Kotlin 2.0

JVM (kotlinc) / Multiplatform · OOP, functional, multiplatform · Static, strong typing, type inference

Recommended Learning Path

First, learn kotlinc compile/run and fun main → grasp variables, types, and control flow → understand null safety (nullable ?, safe call ?., Elvis ?:) and data class → use lambdas, extension functions, and functional collection operations → dig into classes, interfaces, and generics → handle errors with try / runCatching → write concurrency with coroutines and Flow → finally learn networking, time, regex, and Gradle build as needed. The FAQ section is worth revisiting to avoid pitfalls.

1.Hello World and Build Environment

Compile and run Kotlin programs: kotlinc toolchain, scripts, package layout, and command-line arguments.

Minimal Program

Every Kotlin program starts with a top-level fun main() as its entry point; println writes to standard output. Kotlin 2.0 supports a no-argument main.

1
2
3
4
5
6
7
8
fun main() {
println("Hello, world!")
}
// Save as Main.kt and compile with kotlinc
// Top-level functions don't need a class wrapper
// Kotlin 2.0 main can have no parameters
// Can also take arguments: fun main(args: Array<String>)
// No semicolons needed at the end of each line

Run and Build

kotlinc compiles source to JVM bytecode; run with java -jar. Gradle is the mainstream build tool for Kotlin projects.

1
2
3
4
5
6
7
8
9
10
11
// Compile to executable jar:
// kotlinc Main.kt -include-runtime -d hello.jar
// Run:
// java -jar hello.jar
// Compile multiple files:
// kotlinc a.kt b.kt -include-runtime -d app.jar
// Compile to bytecode directory only:
// kotlinc a.kt -d out
// Run source directly (no compile):
// kotlin Main.kt
// Check version: kotlinc -version

Kotlin Script

.kts files can be run directly, ideal for small tools and automation. Scripts don't need a main function; top-level statements execute in order.

1
2
3
4
5
6
7
8
9
10
11
// hello.kts
val name = "Kotlin"
println("Hello, $name!")
// Run: kotlin hello.kts
// Scripts allow top-level expressions:
val items = listOf(1, 2, 3)
println(items.sum())
// Great for prototypes and small everyday tasks
// Project code still uses compiled main
// Third-party jars can be called directly in scripts:
// kotlin -classpath lib.jar app.kts

Package and Import

package declares the file's package; import brings in other package types. Unlike Java, unused imports don't cause errors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.demo
import kotlin.math.sqrt
import kotlin.math.max as mx
import java.util.UUID
fun main() {
println(sqrt(16.0))
println(mx(1, 2))
println(UUID.randomUUID())
}
// Wildcard import:
// import kotlin.math.*
// Top-level functions and classes are public by default
// Unused imports don't cause errors

Command-Line Arguments

The main's Array<String> parameter receives command-line arguments; args[0] is the first. Arguments are always strings and must be converted manually.

1
2
3
4
5
6
7
8
9
10
11
fun main(args: Array<String>) {
println("Total ${args.size} arguments")
args.forEach { println(it) }
// Arguments are always strings, convert manually:
val port = args.firstOrNull()
?.toIntOrNull() ?: 8080
println("Port: $port")
}
// Run: kotlin Main.kt a b c
// args = [a, b, c]
// Use toIntOrNull for robust parsing to avoid exceptions

Output and Formatting

println outputs with a newline; print without. The string template ${} embeds expressions — the most common formatting idiom.

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val name = "Kotlin"
val year = 2026
println("Hello, $name!") // Simple variable
println("This is the ${year - 2025}th anniversary") // Expression
print("No newline")
val pi = 3.14159
println("Pi = %.2f".format(pi)) // Pi = 3.14
println("%5d".format(42)) // " 42"
// printf style (Java interop):
System.out.printf("name=%s%n", name)
}

Exit Code

exitProcess immediately terminates the program with the given exit code; 0 means success, non-zero means failure. Don't call it in library code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import kotlin.system.exitProcess
fun main(args: Array<String>) {
if (args.isEmpty()) {
println("Usage: app <name>")
exitProcess(1)
}
println("Hello, ${args[0]}")
exitProcess(0)
}
// exitProcess exits immediately, without running finally
// 0 success, non-zero failure
// Equivalent to Java's System.exit
// Library code shouldn't call exitProcess

Environment Setup

IntelliJ IDEA Community Edition has built-in Kotlin support, as does Android Studio. For the command line, use kotlinc and Gradle.

1
2
3
4
5
6
7
8
9
10
// IntelliJ IDEA Community Edition (free)
// Create new project, choose Kotlin/JVM
// Android Studio has built-in Kotlin support
// CLI tools: kotlinc + Gradle
// Verify installation:
// kotlinc -version
// gradle -version
// Recommended to configure Aliyun Maven mirror in China
// Beginners don't need Android, just run JVM projects
// Multiplatform projects need the extra Kotlin Multiplatform plugin

2.Variables and Constants

Variable declarations, val/var, constants, type inference, destructuring, and scope.

val and var

val declares a read-only reference; var declares a mutable one. Prefer val — the compiler will suggest val when a var could be val.

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val pi = 3.14159 // Read-only, can't be reassigned
var counter = 0 // Mutable
counter++
// Compile error when reassigning val:
// pi = 3.0
// Object referenced by val is still mutable:
val list = mutableListOf(1)
list.add(2) // OK, reference unchanged
}
// Principle: prefer val over var
// val means the reference is immutable, not the contents

Type Inference

The compiler infers the variable's type from the initializer, so explicit types can be omitted. Inferred types are equivalent to explicit ones.

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val count = 42 // Int
val name = "Kotlin" // String
val ratio = 3.14 // Double
val ok = true // Boolean
// Type is fixed after inference:
// val x = 1 inferred as Int
// Need explicit type only when inference fails:
val empty: List<Int> = emptyList()
val map: Map<String, Int> =
mapOf("a" to 1)
}

Explicit Type

Explicit types improve readability and are often needed for null safety and generics. Types follow the variable name, separated by a colon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val name: String = "Rex"
var age: Int = 30
val height: Double = 1.75
val active: Boolean = true
// Nullable type:
val maybe: String? = null
// Function type variable:
val handler: (Int) -> String =
{ it.toString() }
// Array type:
val nums: IntArray = intArrayOf(1, 2, 3)
}
// Explicit types don't affect inference result
// Recommend explicit types for team collaboration boundaries

Compile-Time Constant

const val declares a compile-time constant — only primitive types and String, and only at the top level or in a companion object.

1
2
3
4
5
6
7
8
9
10
11
12
13
const val MAX_SIZE = 100
const val APP_NAME = "GuruToolkit"
const val RATE = 0.15
fun main() {
println(MAX_SIZE)
}
// Limited to primitives and String
// Must be declared at top level or in companion object
// Use val for runtime-calculated constants:
// val now = System.currentTimeMillis()
// const is inlined at compile time
// Custom types can't use const

Top-Level Variable

Variables and functions can be declared at the top level of a file, without wrapping in a class. Top-level val/var are file-level globals.

1
2
3
4
5
6
7
8
9
10
11
12
13
// File-level (no class wrapper)
val appName = "demo"
var requestCount = 0
fun log(msg: String) {
println("[$appName] $msg")
}
// Top-level declarations are public by default
// Can be referenced directly from other files in the same package
// Add private for internal implementation:
private val secret = "hidden"
// Top-level initializers run at class load time
// Prefer val, avoid mutable global state

Destructuring Declaration

Destructuring splits an object into multiple variables. Pair, Triple, and data classes all support destructuring.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val (name, age) = "Alice" to 30
println(name) // Alice
println(age) // 30
val (x, y) = Pair(1, 2)
val (a, b, c) = Triple(1, 2, 3)
}
// data class supports destructuring automatically:
data class Point(val px: Int, val py: Int)
fun show(p: Point) {
val (px, py) = p
println("$px,$py")
}
// Ignore parts with underscore:
// val (first, _) = listOf(1, 2)

Type Alias

typealias gives an existing type an alias for readability. It doesn't create a new type and is fully equivalent to the original.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typealias UserId = Long
typealias Handler = (String) -> Unit
typealias NameMap = Map<String, String>
fun main() {
val id: UserId = 1001
val handle: Handler = { println(it) }
val names: NameMap = mapOf("a" to "b")
handle(names["a"] ?: id.toString())
}
// Generic alias:
// typealias IntList = List<Int>
// Just an alias, doesn't affect type checking
// Function type aliases make signatures more readable

Scope and Block

Variable scope is determined by curly-brace blocks; inner scopes can access outer ones. Same-named variables can't be declared twice in the same scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val outer = 10
if (outer > 0) {
val inner = outer * 2 // Local to the block
println(inner)
}
// inner is not visible here
// Can't redeclare in the same scope:
// val x = 1; val x = 2 error
// Inner scope can shadow outer variable:
val name = "outer"
println(name)
}
// Shadowing causes confusion, use sparingly

3.Data Types

Numbers, characters, booleans, arrays, ranges, data classes, and type conversions.

Number Types

Kotlin provides six numeric types — Byte/Short/Int/Long/Float/Double — with suffixes L, f, u to specify the exact type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val b: Byte = 127
val s: Short = 32767
val i: Int = 2_147_483_647
val l: Long = 9_000_000_000L // suffix L
val f: Float = 3.14f // suffix f
val d: Double = 3.14159
// Underscore separator improves readability:
val million = 1_000_000
// Hexadecimal and binary:
val hex = 0xFF
val bin = 0b1010
}
// Unsigned types (1.5+): val u: UInt = 42u

Numeric Operations

Arithmetic, bitwise operations, and math functions. Int divided by Int yields Int — note that integer division truncates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlin.math.abs
import kotlin.math.max
fun main() {
val a = 7
val b = 2
println(a / b) // 3 (integer division truncates)
println(a % b) // 1 (remainder)
println(a.toDouble() / b) // 3.5
println(abs(-5))
println(max(3, 9))
// Bitwise operations:
println(1 shl 4) // 16
println(0b1100 and 0b1010)
}

Char and Boolean

Char represents a single character (single quotes); Boolean has only true/false. Characters have full check and conversion methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val letter: Char = 'A'
val digit: Char = '9'
val flag: Boolean = true
// Character methods:
println(letter.isLetter()) // true
println(digit.isDigit()) // true
println(letter.lowercaseChar())
println(letter.code) // 65 ASCII code
// Boolean operations:
println(flag && true)
println(flag || false)
println(!flag)
}
// Escape characters: val tab = '\t'
// Booleans have no 0/1 semantics, can't participate in arithmetic

Arrays

Array<T> is a reference-typed array; IntArray and similar are primitive-typed (more efficient). Array size is fixed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val nums = arrayOf(1, 2, 3) // Array<Int>
val ints = intArrayOf(1, 2, 3) // IntArray
val strs = arrayOf("a", "b")
println(nums[0])
nums[1] = 99
println(nums.size)
// Fixed-size array:
val zeros = IntArray(5) // all 0
// Init lambda:
val squares = IntArray(5) { it * it }
for (n in ints) println(n)
}
// Array covariance trap see FAQ section

Ranges

The range a..b is closed; until is open on the right. Often used with for loops and the in operator.

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val r1 = 1..10 // inclusive 10
val r2 = 1 until 10 // exclusive 10
val r3 = 10 downTo 1 // descending
val r4 = 1..10 step 2 // step 2
println(5 in r1) // true
println(11 in r1) // false
for (i in 1..3) println(i)
// Char range:
for (c in 'a'..'c') print(c)
}
// Ranges are mainly for Int/Long/Char

String Type

String is an immutable sequence of UTF-16 characters. String literals use double quotes; escapes use backslash.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val s: String = "Hello"
val quote = "say \"hi\""
// Immutable: s[0] = 'h' errors
val greeting = "Hi, " + "Kotlin"
println(greeting)
// Iterate and index:
for (c in "abc") println(c)
println("Kotlin"[1]) // 'o'
println("Kotlin".length) // 6
// Substring:
println("Kotlin".substring(1, 4)) // otl
}
// Compare strings with == (structural equality)

data class

data class auto-generates equals/hashCode/toString/copy and destructuring — pure value objects for holding data.

1
2
3
4
5
6
7
8
9
10
11
12
13
data class Point(val x: Int, val y: Int)
fun main() {
val p = Point(1, 2)
val q = p.copy(y = 99) // Copy and modify
println(p) // Point(x=1, y=2)
println(p == Point(1, 2)) // true structural equality
val (x, y) = p // Destructuring
println("$x, $y")
}
// data class primary constructor needs at least one val/var
// copy is shallow, nested objects are still shared
// Ideal for DTOs and value objects

Type Conversion

Kotlin doesn't implicitly convert numeric types; use explicit toXxx() methods. Small-to-large conversions can still overflow — be careful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val i: Int = 42
val l: Long = i.toLong() // Explicit conversion
val d: Double = i.toDouble()
// String to number:
val n = "42".toInt()
val safe = "abc".toIntOrNull() // null
val s = 42.toString()
// Overflow truncation:
val big = 1000.toByte() // -24
}
// No implicit conversions:
// val x: Long = 42 compile error
// Use toXxxOrNull family to avoid exceptions

Unit and Nothing

Unit means no return value (like Java's void). Nothing means never returns — used for functions that always throw or are unimplemented.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun log(msg: String): Unit {
println(msg)
}
// Unit return type can be omitted:
fun log2(msg: String) {
println(msg)
}
// Nothing: never returns
fun fail(msg: String): Nothing {
throw IllegalStateException(msg)
}
// Not-implemented placeholder:
fun todo(): Nothing =
throw NotImplementedError("Not implemented")
// Nothing is a subtype of all types
// Can be used where any expected type is required

4.References and Null Safety

Kotlin has no raw pointers: reference semantics, nullable types, safe call, Elvis, lateinit, and value classes.

References and Objects

Kotlin has no raw pointers; all variables are references. The JVM manages lifetimes automatically — no pointer arithmetic.

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val a = "hello"
val b = a // Same object reference, not a copy
println(a === b) // true same reference
// No & address-of, no * dereference
// Primitives are also objects (boxed when nullable)
val n1 = 100
val n2 = 100
println(n1 == n2) // true structural equality
}
// === compares whether references are the same object
// == compares content equality
// No manual memory release needed, GC handles it automatically

Nullable Types

Appending ? to a type makes it nullable. Nullable types can't call methods directly — null checks are required, the foundation of Kotlin's null safety.

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
var name: String = "Kotlin" // Non-null
var maybe: String? = null // Nullable
// Direct call errors: maybe.length compile error
// Use after null check:
if (maybe != null) {
println(maybe.length)
}
// Compiler smart-casts after check, safe to access
}
// When nullable types come from Java, use annotations:
// @Nullable -> nullable, @NotNull -> non-null
// Java types without annotations are platform types (String!)

Safe Call

?. calls a method on a non-null receiver; otherwise the whole expression is null. Safe calls can be chained.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
println(maybe?.length) // null
val sure: String? = "hi"
println(sure?.length) // 2
}
// Chained safe call:
data class User(val name: String?)
fun show(u: User?) {
val len = u?.name?.length
println(len)
}
// Combine with let to handle non-null:
// maybe?.let { println("non-null: $it") }
// ?. returns a nullable type, must continue handling or provide default

Elvis Operator

?: returns the right side when the left is null. The right side can be an expression, an early return, or a throw.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
val result = maybe ?: "default"
println(result) // default
// Combine with ?. for chained calls:
val len = maybe?.length ?: 0
val upper = maybe?.uppercase() ?: ""
}
// Right side can be an early return:
fun read(input: String?): String {
return input ?: return "empty"
}
// Or throw an exception:
// val value = maybe ?: throw IllegalArgumentException()
// Elvis left side must be nullable, right side non-null

Not-Null Assertion

!! forces a nullable type to non-null, throwing NPE when null. Avoid it; use only when you're certain the value isn't null.

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val maybe: String? = "hello"
val len = maybe!!.length // Force non-null
println(len)
// Throws NullPointerException when null
}
// Better alternatives:
// 1. Safe call + Elvis:
// val l = maybe?.length ?: 0
// 2. Early validation for clear errors:
// val v = maybe ?: error("config missing")
// 3. require() precondition check
// Team review should block abuse of !!

lateinit Property

lateinit declares a non-null var initialized later — for dependency injection or framework callbacks. Accessing before initialization throws.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lateinit var config: String
fun setup() {
config = "initialized"
}
fun use() {
// Check if initialized before access:
if (::config.isInitialized) {
println(config)
}
}
// Limits: only for var, non-null type
// Can't be primitive types (Int, etc.)
// Can't be local variables
// Alternatives: nullable type + null check, or by lazy

by lazy Delegation

by lazy initializes on first access and caches the result. Thread-safe; great for expensive one-shot initialization.

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val config: String by lazy {
println("Executed on first access")
"computed config"
}
println(config) // Prints init log
println(config) // Uses cache, doesn't re-execute
}
// Specify thread safety mode:
// val v by lazy(LazyThreadSafetyMode.PUBLICATION)
// Default is SYNCHRONIZED, thread-safe
// Read-only, suitable for val
// Dependency injection commonly uses lateinit or by lazy

Copy and Equality

== compares structural equality; === compares reference identity. data class copy() does a shallow copy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
data class Person(val name: String, val age: Int)
fun main() {
val a = Person("Alice", 30)
val b = Person("Alice", 30)
println(a == b) // true content equal
println(a === b) // false different objects
val c = a.copy(age = 31)
println(c) // Person(name=Alice, age=31)
}
// Shallow copy shares nested objects:
data class Wrapper(val list: MutableList<Int>)
fun test() {
val w1 = Wrapper(mutableListOf(1))
val w2 = w1.copy()
w2.list.add(2)
println(w1.list) // [1, 2] shared!
}
// Deep copy requires manual implementation or serialization

value class

value class wraps a single value and is inlined at runtime, eliminating boxing overhead — a type-safe custom wrapper.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@JvmInline
value class UserId(val id: Long)
fun process(userId: UserId) {
println(userId.id)
}
fun main() {
val id = UserId(1001)
process(id)
}
// Requires @JvmInline annotation (Kotlin 1.5+)
// Only one property
// Inlined at compile time, no wrapper object at runtime
// Ideal for wrapper types: UserId, Money, etc.
// Can't be used for open classes, array elements, etc.

5.Control Flow

if, when, for, while, and smart casts—expression-style control flow.

if Expression

if is an expression that can return a value to a variable. Kotlin has no ternary — if-else replaces it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val score = 85
val grade = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else {
"C"
}
println(grade) // B
// Single-line form:
val sign = if (score > 0) "positive" else "negative"
println(sign)
}
// Branch return types must be the same or share a common supertype

when Expression

when replaces switch, supporting values, ranges, types, and conditional branches. Expression form must have an else branch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun main() {
val status = 404
val msg = when (status) {
200 -> "OK"
404 -> "Not Found"
in 500..599 -> "Server Error"
else -> "Other"
}
println(msg)
// No-arg when (like if-else chain):
val n = 10
when {
n > 0 -> println("positive")
n < 0 -> println("negative")
else -> println("zero")
}
// Merge multiple branches:
when (status) {
200, 201 -> println("success")
else -> println("other")
}
}

for Loop

for iterates over ranges, collections, arrays, and other iterables. Kotlin has no C-style three-expression for.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// Iterate range:
for (i in 1..5) println(i)
// Iterate collection:
val items = listOf("a", "b", "c")
for (item in items) println(item)
// With index:
for ((index, value) in items.withIndex()) {
println("$index -> $value")
}
// Iterate map:
val map = mapOf("a" to 1, "b" to 2)
for ((k, v) in map) println("$k=$v")
}
// Descending: for (i in 5 downTo 1)
// Step: for (i in 0 until 10 step 2)

while Loop

while checks then executes; do-while executes at least once. Conditions must be Boolean expressions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
var n = 0
while (n < 3) {
println(n)
n++
}
// do-while runs at least once:
var m = 5
do {
println(m)
m++
} while (m < 3)
// Infinite loop:
while (true) {
break
}
}
// Remember to update loop variable to avoid infinite loops
// Prefer for / forEach for collections

break and continue

break exits the loop; continue skips the current iteration. Labels with @ control jumps in nested loops.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
for (i in 1..5) {
if (i == 3) continue // skip 3
if (i == 5) break // exit loop
println(i) // 1 2 4
}
// Label break out outer:
outer@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) continue@outer
println("$i,$j")
}
}
// Output: 1,1 2,1 3,1
}
// Label can also be used with return: return@label

Smart Cast

After a type check, the compiler auto-casts the type — no explicit cast needed. Null and is checks enable safe use.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Rect(val w: Double, val h: Double) : Shape()
fun area(shape: Shape): Double = when (shape) {
is Circle -> Math.PI * shape.r * shape.r
is Rect -> shape.w * shape.h
}
fun main() {
val obj: Any = "hello"
if (obj is String) {
println(obj.length) // auto-cast to String
}
println(area(Circle(1.0)))
}
// Prerequisite: variable isn't modified after the check
// Use explicit cast as only when necessary

Labels and Jumps

Labels marked with @ tag a loop or lambda, enabling precise jumps with break, continue, and return.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
// Loop label:
outer@ for (i in 1..3) {
for (j in 1..3) {
if (i * j > 4) break@outer
println("$i x $j")
}
}
// Lambda label return:
val list = listOf(1, 2, 3, 4)
list.forEach {
if (it == 3) return@forEach // skip this round
println(it) // 1 2
}
}
// Return without a label exits the enclosing function
// Label can be named anything: myLoop@

takeIf and takeUnless

takeIf returns the receiver if the predicate holds, else null; takeUnless is the opposite. Often used for chained null checks and filters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val n = 10
val positive = n.takeIf { it > 0 }
println(positive) // 10
val neg = n.takeUnless { it > 0 }
println(neg) // null
// Combine with ?.:
val text: String? = "hello"
val upper = text?.takeIf { it.isNotBlank() }
?.uppercase()
println(upper)
// Filter and check null:
val list = listOf(1, 2, 3)
val first = list.firstOrNull()
?.takeIf { it > 5 }
println(first) // null
}

6.Functions and Lambdas

Function declarations, lambdas, higher-order functions, extension functions, scope functions, and infix functions.

Function Declaration

fun declares a function; parameters have types, return type follows the parameter list. Use Unit for no return value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun add(a: Int, b: Int): Int {
return a + b
}
fun greet(name: String) {
println("Hello, $name")
}
// No return type defaults to Unit:
fun log(msg: String) { println(msg) }
fun main() {
val sum = add(1, 2)
greet("Kotlin")
log("sum = $sum")
}
// Parameters are passed by value (reference types pass the reference)
// Functions are public by default

Single-Expression Function

When a function body is a single expression, use = as shorthand to omit return and braces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun square(x: Int) = x * x
fun isEven(n: Int) = n % 2 == 0
fun desc(n: Int) = if (n > 0) "positive" else "non-positive"
// Equivalent form:
fun square2(x: Int): Int {
return x * x
}
fun main() {
println(square(5)) // 25
println(isEven(4)) // true
println(desc(-1)) // non-positive
}
// Suited for simple pure functions
// Use block body for complex logic for better readability

Default and Named Arguments

Parameters can have default values, omitted at the call site. Named arguments can be passed in any order for clarity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun greet(name: String, prefix: String = "Hi") {
println("$prefix, $name!")
}
fun main() {
greet("Alice") // Hi, Alice!
greet("Bob", "Hello") // Hello, Bob!
// Named argument (skip default):
greet(name = "Cathy", prefix = "Hello")
// Order can be changed:
greet(prefix = "Hola", name = "Dan")
}
// Arguments after a default value:
// fun format(v: Int, base: Int = 10, pad: Int = 0)
// Use named arguments for boolean parameters for clarity

vararg

vararg accepts any number of arguments; inside the function it's an Array. Use the spread operator * to pass an array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun sum(vararg nums: Int): Int {
return nums.sum()
}
fun main() {
println(sum(1, 2, 3)) // 6
println(sum(1, 2, 3, 4, 5)) // 15
// Spread an array when passing:
val arr = intArrayOf(10, 20)
println(sum(*arr)) // 30
}
// vararg parameter type is Array:
// vararg nums: Int -> IntArray
// Mix with normal parameters:
// fun build(prefix: String, vararg parts: String)
// Usually placed at the end of the parameter list

Lambda Expression

A lambda is an anonymous function wrapped in braces; -> separates parameters from the body. When the last parameter is a lambda, you can use trailing-lambda syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val sum = { a: Int, b: Int -> a + b }
println(sum(1, 2)) // 3
// Single parameter omits declaration, use it:
val list = listOf(1, 2, 3)
println(list.map { it * 2 }) // [2, 4, 6]
// Trailing lambda syntax:
list.filter { it > 1 }
.forEach { println(it) }
// Explicit types:
val greet: (String) -> String =
{ "Hello, $it" }
println(greet("Kotlin"))
}
// Lambdas can capture enclosing variables

Higher-Order Function

Higher-order functions take functions as parameters or return them. Standard-library higher-order functions like map/filter are daily workhorses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun applyTwice(f: (Int) -> Int, x: Int): Int {
return f(f(x))
}
fun main() {
val inc = { n: Int -> n + 1 }
println(applyTwice(inc, 5)) // 7
// Common higher-order functions:
val nums = listOf(1, 2, 3, 4)
println(nums.map { it * 2 }) // transform
println(nums.filter { it % 2 == 0 }) // filter
println(nums.fold(0) { acc, n -> acc + n }) // accumulate
println(nums.any { it > 3 }) // any matches
println(nums.all { it > 0 }) // all match
}
// Function returning a function:
fun makeAdder(step: Int): (Int) -> Int = { it + step }

Extension Function

Extension functions add methods to existing classes without modifying them. this refers to the receiver; scope is limited to the declaration site.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
fun main() {
println("level".isPalindrome()) // true
println("hello".isPalindrome()) // false
// Generic extension:
println(listOf(1, 2).secondOrNull())
}
// Generic extension definition:
fun <T> List<T>.secondOrNull(): T? =
if (size >= 2) this[1] else null
// Called like ordinary methods
// Extensions don't change the receiver type

Infix Function

The infix keyword lets a function be called with spaces, like an operator. Requires a member or extension function with a single parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
infix fun Int.times(str: String): String {
return str.repeat(this)
}
fun main() {
println(3 times "hi") // hihihi
// Standard library infix:
val map = mapOf("a" to 1) // to is infix
println(2 in 1..3) // in is infix
}
// Custom infix:
// infix fun String.concat(b: String) = this + b
// println("foo" concat "bar") // foobar
// Requirements: member or extension, single parameter, no default value

Scope Function

let/run/with/apply/also execute code in an object's context. let returns the result; apply returns the receiver.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data class User(var name: String, var age: Int)
fun main() {
val user = User("Alice", 30)
// let: returns the result, it references the object
val greeting = user.let { "Hi ${it.name}" }
println(greeting)
// apply: returns the receiver, often used for initialization
val u2 = User("Bob", 0).apply {
age = 25
}
println(u2)
// run: returns the result, this references the object
val info = user.run { "$name is $age years old" }
println(info)
// also: returns the receiver, used for side effects
user.also { println(it.name) }
// with: not an extension, this references the object
val desc = with(user) { "$name/$age" }
println(desc)
}

Local Function

Local functions can be defined inside functions, capturing outer variables. Useful for organizing logic and reducing duplication.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun process(nums: List<Int>) {
fun isPositive(n: Int) = n > 0
fun show(label: String, v: Int) {
println("$label: $v")
}
val positives = nums.filter(::isPositive)
show("positive count", positives.size)
}
// Local functions can access enclosing variables:
fun stats(nums: List<Int>) {
val total = nums.sum()
fun percent(n: Int) = n * 100.0 / total
println(percent(1))
}
fun main() {
process(listOf(-1, 2, -3, 4))
stats(listOf(1, 2))
}
// Only visible within the enclosing function
// Good for encapsulating recursive helper functions

7.Strings

String basics, templates, raw strings, methods, concatenation, formatting, and conversions.

String Basics

String is an immutable character sequence. Double-quoted literals, escape sequences, index access, and iteration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val s = "Hello, Kotlin"
println(s.length) // 13
println(s[0]) // 'H'
println(s.first()) // 'H'
println(s.last()) // 'n'
println(s.isEmpty()) // false
println(s.isBlank()) // false
println(s.lowercase())
println(s.uppercase())
// Prefix and suffix:
println(s.startsWith("Hello")) // true
println(s.endsWith("lin")) // true
println(s.contains("Kot")) // true
}
// Immutable: each modification returns a new string

String Template

$variable and ${expression} embed values into strings — the most common string-building idiom.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val name = "Kotlin"
val year = 2026
println("Hello, $name!") // Hello, Kotlin!
println("This is the ${year - 2025}th anniversary")
// Object property:
val u = User("Alice")
println("User: ${u.name}")
// Method call:
val list = listOf(1, 2)
println("Count: ${list.size}")
// Escape $:
println("\$100")
}
data class User(val name: String)
// Complex expressions must use curly braces

Raw String

Triple-quoted """ raw strings preserve newlines and formatting without escaping. trimIndent strips common indentation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
// Raw string: wrapped in triple double quotes, no escaping
val path = """C:\Users\name\docs"""
println(path) // backslashes preserved as-is
// Multi-line raw string preserves newlines and spaces:
// val sql = """
// SELECT *
// FROM users
// """.trimIndent()
// trimIndent() removes common leading indentation
// Templates still work:
val name = "Kotlin"
val msg = """Hello $name"""
println(msg)
}
// No need to escape backslashes or quotes
// Great for multi-line text, SQL, JSON
// Single-line raw strings are also valid: """abc"""

Common Methods

The standard library provides rich string methods: substring, replace, emptiness checks, trimming, padding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val s = " Hello World "
println(s.trim()) // strip whitespace
println(s.substring(2, 7)) // "Hello"
println(s.replace("World", "Kt"))
println(s.reversed())
println(s.padStart(20, '*'))
println("".isEmpty()) // true
println(" ".isBlank()) // true
// Index lookup:
val t = "banana"
println(t.indexOf('a')) // 1
println(t.lastIndexOf('a')) // 5
println(t.indexOf("na")) // 2
println("ab".repeat(3))
}

Concatenation and StringBuilder

Use StringBuilder (or buildString) in loops to avoid creating many intermediate strings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
// For few concatenations, use + or templates:
val a = "foo"
val b = "bar"
println("$a-$b")
// For loop concatenation, use StringBuilder:
val sb = StringBuilder()
for (i in 1..5) {
sb.append("item$i; ")
}
println(sb)
// Functional buildString:
val result = buildString {
append("start")
for (i in 1..3) append(i)
}
println(result) // start123
// joinToString for collections:
println(listOf("a", "b").joinToString(", "))
}
// + in a loop repeatedly creates new strings

Formatting

The format method implements printf-style formatting with width, precision, and type placeholders.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val name = "Kotlin"
val score = 92.5
println("Name: %s".format(name))
println("Score: %.1f".format(score)) // 92.5
println("Padded: %05d".format(42)) // 00042
println("Left-aligned: %-8s|".format(name))
println("%x".format(255)) // ff
println("%b".format(true)) // true
// Specify width:
println("%10s".format(name))
}
// Java's String.format equivalent:
// String.format("Hello %s", name)
// Format string errors don't cause compile errors, throw at runtime

String Conversion

Convert between strings and numbers, or to Boolean. The toXxxOrNull family returns null on failure instead of throwing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val s = "42"
println(s.toInt()) // 42
println(s.toLong())
println("3.14".toDouble())
// Safe conversion, returns null on failure:
println("abc".toIntOrNull()) // null
println("42".toIntOrNull() ?: 0)
// Number to string:
println(42.toString())
println(3.14.toString())
// Boolean conversion:
println("true".toBooleanStrictOrNull())
// Convert to char array:
val chars = "hi".toCharArray()
println(chars.size)
}

Split and Join

split divides by delimiters into a list; joinToString joins a collection into a string. Multiple delimiters supported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val csv = "a,b,c"
val parts = csv.split(",")
println(parts) // [a, b, c]
// Multiple delimiters:
println("a-b_c".split('-', '_'))
// Collection to string:
val nums = listOf(1, 2, 3)
println(nums.joinToString(", ")) // 1, 2, 3
println(nums.joinToString(", ", "[", "]"))
// Limit number of splits:
println("a,b,c".split(",", limit = 2))
// Regex delimiter:
println("a, b; c".split(Regex("[,;\\s]+")))
}

8.Collections

List, Set, Map, lazy sequences, transformations, grouping, and sorting.

List

listOf creates a read-only list; elementAt and get access by index. The default List interface is not modifiable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val nums = listOf(1, 2, 3, 2)
println(nums[0]) // 1
println(nums.elementAt(1)) // 2
println(nums.size) // 4
println(nums.first()) // 1
println(nums.last()) // 2
println(nums.indexOf(2)) // 1
println(nums.contains(3)) // true
// Empty list:
val empty = emptyList<Int>()
println(empty)
// Build a list of N elements:
println(List(3) { it * 10 }) // [0, 10, 20]
}
// Out-of-bounds throws IndexOutOfBounds
// firstOrNull avoids exceptions

Set

setOf creates a collection of unique elements. Membership checks are faster than List.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val tags = setOf("a", "b", "a")
println(tags) // [a, b] auto dedupe
println("a" in tags) // true
println(tags.contains("c")) // false
// Set operations:
val s1 = setOf(1, 2, 3)
val s2 = setOf(2, 3, 4)
println(s1 intersect s2) // [2, 3]
println(s1 union s2) // [1, 2, 3, 4]
println(s1 subtract s2) // [1]
println(s1.isEmpty())
}
// Preserves insertion order (LinkedHashSet)
// Use toSet for deduplication

Map

mapOf creates key-value pairs; to or Pair constructs entries. Get by key; missing keys can return a default.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"]) // 30
println(ages["Cathy"]) // null
// Safe access:
println(ages.getValue("Alice"))
println(ages.getOrDefault("Cathy", 0))
println(ages.getOrElse("Cathy") { -1 })
// Check key existence:
println("Alice" in ages)
println(ages.containsKey("Bob"))
// Iterate:
for ((k, v) in ages) println("$k=$v")
println(ages.keys)
println(ages.values)
}

Mutable Collections

mutableListOf/mutableSetOf/mutableMapOf create mutable collections. The read-only view can't add.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
val list = mutableListOf(1, 2)
list.add(3) // tail
list.add(0, 0) // specific position
list.removeAt(1)
list[0] = 99
println(list) // [99, 2, 3]
// Mutable set:
val set = mutableSetOf("a")
set.add("b")
set.remove("a")
// Mutable map:
val map = mutableMapOf("a" to 1)
map["b"] = 2
map.remove("a")
// Bulk operations:
list.addAll(listOf(4, 5))
list.removeAll(listOf(2, 3))
println(list)
}
// Read-only views still reference the same underlying data

Lazy Sequence

Sequence is lazy; chained operations execute element-by-element at the terminal operation. Avoids intermediate collections for large data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val nums = (1..100).asSequence()
.filter { it % 2 == 0 }
.map { it * it }
.take(5)
.toList()
println(nums) // [4, 16, 36, 64, 100]
// Infinite sequence:
val naturals = generateSequence(1) { it + 1 }
naturals.take(3).forEach { println(it) }
}
// Difference from List:
// List chain creates a new collection at each step
// Sequence processes in a single pass, saving memory
// Use Sequence for large data volumes
// Terminal operations: toList, sum, count

Transform Operations

map/filter/flatMap are collection-processing workhorses, replacing hand-written loops with functional style.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
val nums = listOf(1, 2, 3, 4)
println(nums.map { it * 2 })
println(nums.filter { it % 2 == 0 })
println(nums.flatMap { listOf(it, it) })
// Aggregations:
println(nums.sum())
println(nums.average())
println(nums.max())
println(nums.fold(0) { acc, n -> acc + n })
println(nums.reduce { acc, n -> acc + n })
// Grouping:
println(nums.groupBy { it % 2 == 0 })
// Dedup and sort:
println(nums.distinct())
println(nums.sorted())
println(nums.first { it > 2 })
}

Grouping and Associating

groupBy groups by condition; associate builds key-value relationships; partition splits into two.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
data class User(val name: String, val dept: String)
val users = listOf(
User("A", "dev"), User("B", "dev"),
User("C", "ops"),
)
// Group:
val byDept = users.groupBy { it.dept }
println(byDept.keys) // [dev, ops]
// associate builds a map:
val nameToDept = users.associate {
it.name to it.dept
}
println(nameToDept)
// Partition:
val nums = listOf(1, 2, 3, 4, 5)
val (even, odd) = nums.partition { it % 2 == 0 }
println(even) // [2, 4]
println(odd) // [1, 3, 5]
println(nums.count { it > 3 })
}

Sorting

sorted/sortedBy return new sorted lists; sort sorts mutable lists in place. Custom comparators supported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
val nums = listOf(3, 1, 2)
println(nums.sorted()) // [1, 2, 3]
println(nums.sortedDescending()) // [3, 2, 1]
data class User(val name: String, val age: Int)
val users = listOf(User("A", 30), User("B", 20))
println(users.sortedBy { it.age })
println(users.sortedByDescending { it.name })
// Multiple fields:
println(users.sortedWith(
compareBy<User> { it.age }.thenBy { it.name }
))
// In-place sort (mutable collection):
val m = mutableListOf(3, 1, 2)
m.sort()
println(m)
}
// reversed() reverses the order

Collection Conversion

Convert between collections, arrays, and Maps. toList/toSet/toMap/toTypedArray.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val list = listOf(1, 2, 2, 3)
println(list.toSet()) // [1, 2, 3]
println(list.toMutableList())
// Array interop:
val arr = intArrayOf(1, 2, 3)
println(arr.toList())
println(listOf(1, 2).toIntArray().contentToString())
// List to Map:
val pairs = listOf("a" to 1, "b" to 2)
println(pairs.toMap())
// String to collection:
println("a,b,c".split(","))
}
// toSet auto-deduplicates
// Type inference for empty collection: listOf<Int>()

9.Memory and Performance

JVM garbage collection, object references, boxing, allocation optimization, and performance analysis.

JVM Garbage Collection

Kotlin/JVM uses GC for automatic memory management — no manual freeing. Unreachable objects are reclaimed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Kotlin runs on the JVM, memory is auto-managed
// No manual release needed, unreachable objects are collected
fun create() {
val temp = StringBuilder() // local object
// Becomes garbage after the function returns
}
fun main() {
// Many temporary objects trigger GC:
for (i in 1..100000) {
val s = "item$i"
}
// Reduce allocation to lower GC pressure:
val sb = StringBuilder()
for (i in 1..100000) {
sb.clear()
sb.append("item$i")
}
println(sb.length)
}
// WeakReference: cache doesn't block collection

Object References

All Kotlin objects are accessed via references — no pointers. Reference types determine sharing and reachability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.ref.WeakReference
fun main() {
val list1 = mutableListOf(1, 2)
val list2 = list1 // same object
list2.add(3)
println(list1) // [1, 2, 3] shared!
}
// Weak reference: doesn't block GC
fun demo() {
val weak = WeakReference(mutableListOf(1))
println(weak.get()) // may be null
}
// SoftReference collected only when memory is low
// Circular references are handled by GC (reachability analysis)

inline Function

inline expands the function body at the call site, eliminating Lambda object overhead. reified generics require inline.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun measure(block: () -> Unit) {
val start = System.nanoTime()
block()
println("Duration: ${System.nanoTime() - start}")
}
// inline expands in place, zero lambda object overhead:
inline fun measureInline(block: () -> Unit) {
val start = System.nanoTime()
block()
}
fun main() {
measureInline { Thread.sleep(1) }
}
// reified generics need to be paired with inline:
inline fun <reified T> cast(obj: Any): T? =
obj as? T
// Excessive inlining increases bytecode size

Allocation Optimization

Reducing object allocation is key to JVM performance. Reuse objects, avoid boxing, use primitive-typed arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// Avoid boxing:
val ints = IntArray(100) // primitive
val boxed = Array(100) { 0 } // boxed Int
// Avoid intermediate collections:
val result = (1..1000)
.asSequence()
.filter { it % 2 == 0 }
.toList()
println(ints.size + boxed.size + result.size)
// Reuse mutable collections:
val buffer = mutableListOf<Int>()
println(buffer)
}
// Use buildString for string concatenation
// BAD: in-loop + concatenation repeatedly creates strings

Primitive Boxing

Non-null primitive local variables use raw types; nullable or generic types are boxed. value class optimizes wrappers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
var count = 0 // primitive int
// Boxed when nullable or in collections:
val maybe: Int? = null // Integer
val list = listOf(1, 2) // List<Integer>
// Arrays:
val raw = intArrayOf(1) // int[]
val boxed = arrayOf(1) // Integer[]
println(list.size)
}
// Primitive types cannot be == null
// Boxed == vs === behave differently
// Avoid boxing on hot paths:
fun sum(raw: IntArray): Long {
var total = 0L
for (n in raw) total += n
return total
}
// value class inline removes wrapper objects

Performance Tuning

Measure before optimizing. Common targets: data structures, repeated computation, lazy evaluation, and algorithmic complexity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
// 1. Pick the right collection:
val set = setOf("a", "b") // lookup O(1)
val list = listOf("a", "b") // lookup O(n)
println(set.contains("a"))
println(list.size)
// 2. Avoid recomputation:
val size = list.size // cache to local
println(size)
// 3. Lazy evaluation saves intermediate collections:
val result = (1..1000)
.asSequence()
.filter { it % 2 == 0 }
.take(10)
.toList()
println(result)
}
// Measure before optimizing, prefer algorithmic complexity
// Avoid premature optimization, run benchmarks first

Object Pool

Reuse objects in high-concurrency scenarios to avoid frequent allocation. Pool objects must be thread-safe and cleared before return.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Connection
object Pool {
private val available = ArrayDeque<Connection>()
fun acquire(): Connection =
available.removeLastOrNull() ?: Connection()
fun release(c: Connection) {
if (available.size < 10) {
available.addLast(c)
}
}
}
fun main() {
val conn = Pool.acquire()
// use...
Pool.release(conn)
}
// Object pool is great for expensive objects: connections, threads, buffers
// Clean up state before returning
// Discard when pool is full to avoid memory bloat

Performance Profiling

Use JFR, VisualVM, etc. to analyze CPU and memory. Heap dumps locate memory leaks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Common JVM profiling tools:
// 1. JFR: java -XX:StartFlightRecording=...
// 2. VisualVM: GUI view of heap and CPU
// 3. jcmd: jcmd <pid> GC.heap_dump
// Quick CLI views:
// jps list Java processes
// jstat -gc <pid> view GC stats
// Memory leak investigation:
// 1. Capture heap dump: jmap -dump:live,file=h.bin <pid>
// 2. Use MAT / VisualVM to analyze the dominator tree
// 3. Check static collections for unbounded growth
// Common leaks: static caches, unregistered listeners
// Inline measurement:
fun main() {
val start = System.nanoTime()
// business code...
println(System.nanoTime() - start)
}

10.Object-Oriented Programming

Classes, constructors, properties, inheritance, interfaces, generics, data classes, and sealed classes.

Class Definition

class declares a class; properties can go in the primary constructor or body. Classes are final by default — not inheritable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
var name: String = ""
var age: Int = 0
fun introduce() {
println("I am $name, $age years old")
}
}
fun main() {
val p = Person()
p.name = "Alice"
p.age = 30
p.introduce()
}
// Classes are final by default, add open to inherit
// Properties must have an initial value or initializer
// Getter/setter are auto-generated

Constructors

The primary constructor is declared in the class header; parameters can directly become properties. init blocks run after primary construction.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User(
val name: String,
val age: Int = 0, // default value
) {
init {
println("Initializing $name")
}
// Secondary constructor:
constructor(name: String, age: Int, email: String) :
this(name, age) {
println(email)
}
}
fun main() {
val u = User("Alice", 30)
val u2 = User("Bob")
val u3 = User("Cathy", 20, "[email protected]")
}
// Add val/var to primary constructor params to make them properties
// Without a modifier they're just constructor parameters
// init blocks run in declaration order

Properties

Properties auto-generate getter/setter. Accessors can be customized; field refers to the backing field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Circle {
var radius: Double = 1.0
// Custom getter:
val area: Double
get() = Math.PI * radius * radius
// Custom setter:
var diameter: Double
get() = radius * 2
set(value) {
radius = value / 2
}
}
fun main() {
val c = Circle()
c.radius = 10.0
println(c.area) // 314.159...
c.diameter = 20.0 // goes through setter
println(c.radius) // 10.0
}
// Private setter: var x set private set
// Properties are accessors, not fields

Inheritance and Overriding

An open class can be inherited; open members can be overridden. override marks an override; super accesses the parent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open class Animal(val name: String) {
open fun speak() = "$name made a sound"
}
class Dog(name: String) : Animal(name) {
override fun speak() = "$name: woof"
}
class Cat(name: String) : Animal(name) {
override fun speak() = super.speak() + ", meow"
}
fun main() {
val a: Animal = Dog("Wangcai")
println(a.speak()) // polymorphism
val cat = Cat("Mimi")
println(cat.speak())
}
// Classes are final by default, add open to inherit
// override members are open by default
// Construction order: parent init runs first

Interface

Interfaces define behavior contracts; they may include default implementations and abstract members. A class can implement multiple interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Drawable {
fun draw()
fun resize(factor: Double) { // default implementation
println("resize x$factor")
}
}
interface Clickable {
fun click()
}
class Button : Drawable, Clickable {
override fun draw() = println("Drawing button")
override fun click() = println("Clicked")
}
fun main() {
val btn = Button()
btn.draw()
btn.resize(2.0) // default implementation
}
// Interfaces can declare properties: val name: String
// Interfaces hold no state (cannot store field values)
// Same-signature methods must be overridden to resolve ambiguity

Abstract Class

abstract class defines a partial implementation; abstract members must be implemented by subclasses. Unlike interfaces, abstract classes can hold state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Shape {
abstract fun area(): Double
fun describe() = "Area: ${area()}"
}
class Square(val side: Double) : Shape() {
override fun area() = side * side
}
class Circle(val r: Double) : Shape() {
override fun area() = Math.PI * r * r
}
fun main() {
val shapes = listOf(Square(2.0), Circle(1.0))
shapes.forEach { println(it.describe()) }
}
// Abstract classes can have properties and constructors
// Prefer interfaces for more flexibility
// Abstract members must be implemented in subclasses

data class

data class auto-generates equals/hashCode/toString/copy/componentN — ideal for value objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data class Point(val x: Int, val y: Int)
fun main() {
val p1 = Point(1, 2)
val p2 = Point(1, 2)
println(p1 == p2) // true structural equality
println(p1.hashCode() == p2.hashCode())
println(p1.toString()) // Point(x=1, y=2)
val p3 = p1.copy(y = 5)
println(p3) // Point(x=1, y=5)
val (x, y) = p1 // destructuring
println("$x,$y")
}
// Primary constructor needs at least one val/var parameter
// copy is shallow
// Great for DTOs, result objects, comparison keys

Sealed Class

sealed class restricts subclasses to the same package/module; when branches can be exhaustive. Expresses limited hierarchies.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sealed class Result {
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
}
fun handle(result: Result): String = when (result) {
is Result.Success -> "Success: ${result.data}"
is Result.Failure -> "Failure: ${result.error}"
}
fun main() {
println(handle(Result.Success("ok")))
}
// Subclasses must be in the same package/module
// No else needed when when is exhaustive
// Compiler reminds you when adding new subclasses
// More flexible than enums, can carry data

Singleton Object

object declares a singleton, lazily initialized in a thread-safe way. Only one instance exists for the entire program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object Logger {
private var count = 0
fun log(msg: String) {
count++
println("[$count] $msg")
}
}
fun main() {
Logger.log("Start")
Logger.log("End")
}
// object is a singleton: one global instance
// Lazy-loaded, created on first access
// Thread-safe
// No constructor
// Use cases: utility classes, configuration, registries

Companion Object

companion object provides class-level members, similar to Java static. Use @JvmStatic to expose them to Java.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Calculator {
companion object {
const val VERSION = "1.0"
fun create() = Calculator()
// Expose as a static method:
// @JvmStatic fun create() = Calculator()
}
}
fun main() {
println(Calculator.VERSION)
val calc = Calculator.create()
println(calc)
}
// Companion object is part of the class
// Access: ClassName.member
// Companion object can be named:
// companion object Factory { }
// A class can only have one companion object

Generics

Generics parameterize types. in/out declare variance; generic functions and type constraints are supported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Box<T>(val value: T)
fun <T> List<T>.firstOr(default: T): T =
if (isEmpty()) default else first()
fun main() {
val box = Box(42) // Box<Int>
val s = Box("hello") // Box<String>
println(listOf(1, 2).firstOr(0))
println(emptyList<Int>().firstOr(-1))
println(box.value + s.value.length)
}
// Type constraint:
fun <T : Comparable<T>> bigger(a: T, b: T): T =
if (a > b) a else b
// Covariance out / Contravariance in:
interface Producer<out T> { fun get(): T }
interface Consumer<in T> { fun put(item: T) }
// Star projection: List<*>

Delegation

The by keyword enables interface delegation and delegated properties. lazy/observable manage properties, simplifying composition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import kotlin.properties.Delegates
interface Base {
fun print()
}
class BaseImpl : Base {
override fun print() = println("Base implementation")
}
class Derived(b: Base) : Base by b // delegate
fun main() {
val d = Derived(BaseImpl())
d.print() // delegated to BaseImpl
}
// Delegated property:
var observable: String by Delegates.observable("init") {
_, old, new -> println("$old -> $new")
}
// Lazy delegation:
val heavy: String by lazy { "expensive" }
// Delegate to a map (dynamic properties):
class Person(map: Map<String, Any>) {
val name: String by map
}

11.Error Handling

try expressions, custom exceptions, runCatching, Result, require/check, and resource management.

try Expression

try is an expression; it can return a value from catch. catch matches exception types; finally always executes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun parse(text: String): Int = try {
text.toInt()
} catch (e: NumberFormatException) {
-1
}
fun risky() {
// Simulate a possibly failing call
}
fun main() {
println(parse("42")) // 42
println(parse("abc")) // -1
// finally block:
try {
risky()
} catch (e: Exception) {
println("Caught: ${e.message}")
} finally {
println("Always runs")
}
}
// Multiple catch blocks match by exception type

Throwing Exceptions

throw throws an exception object. Custom messages and exception chains. require/check are idiomatic for validation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun validate(age: Int) {
if (age < 0) {
throw IllegalArgumentException("Age cannot be negative")
}
}
fun main() {
try {
validate(-1)
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
// Throw custom message:
// throw IllegalStateException("Illegal state")
// With cause chain:
// throw RuntimeException("Upper error", Throwable("Underlying"))
// Parameter validation idiom:
// require(age >= 0) { "Age must be non-negative" }
// requireNotNull(text)
// check(state == "ready") { "Not ready" }

Custom Exception

Subclass Exception or RuntimeException to define custom exceptions. Can carry extra fields.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ValidationError(
val field: String,
message: String,
) : Exception("Field $field invalid: $message")
fun parseUser(input: String) {
if (input.isBlank()) {
throw ValidationError("input", "cannot be blank")
}
}
fun main() {
try {
parseUser("")
} catch (e: ValidationError) {
println("Field: ${e.field}")
println("Message: ${e.message}")
}
}
// Extend Exception or RuntimeException
// Kotlin has no checked-exception restriction
// Use data class for exceptions to enable equality comparison

runCatching

runCatching catches exceptions and returns a Result — a functional-style alternative to try-catch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun risky() = 42
fun main() {
val result = runCatching { "42".toInt() }
println(result.getOrNull()) // 42
val failed = runCatching { "x".toInt() }
println(failed.getOrNull()) // null
// Default value:
val n = runCatching { "x".toInt() }
.getOrDefault(0)
println(n)
// Branch handling:
runCatching { risky() }
.onSuccess { println("Success: $it") }
.onFailure { println("Failure: ${it.message}") }
// Transform:
val len = runCatching { "hi" }
.map { it.length }
.getOrDefault(-1)
println(len)
}
// Note runCatching also catches CancellationException

Result Type

Result<T> wraps a success value or failure exception, supporting map/onSuccess etc. Commonly used as a return type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun divide(a: Int, b: Int): Result<Int> =
runCatching { a / b }
fun main() {
val ok = divide(10, 2)
val bad = divide(10, 0)
println(ok.getOrNull()) // 5
println(bad.exceptionOrNull()?.message)
// Chain operations:
divide(10, 2)
.map { it * 2 }
.onSuccess { println("Result $it") }
.onFailure { println("Failed") }
// Unwrap (throws on failure):
val v = divide(10, 2).getOrThrow()
println(v)
}
// Result is used to wrap a single value
// Use fold to aggregate multiple Results in a collection

Resource Management

The use extension auto-closes Closeable/AutoCloseable resources — like Java's try-with-resources.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.File
fun readFirstLine(path: String): String? =
File(path).bufferedReader().use { reader ->
reader.readLine()
}
fun main() {
println(readFirstLine("data.txt"))
}
// use ensures finally auto-closes:
// File("f").bufferedReader().use { it.readText() }
// useLines for streaming line by line:
fun first10(path: String) {
File(path).useLines { lines ->
lines.take(10).forEach { println(it) }
}
}
// Custom resource:
class Resource : AutoCloseable {
override fun close() = println("Closed")
}
fun demo() {
Resource().use { println("In use") }
}

require and check

require validates arguments; check validates state; error actively fails. They throw and print the message on failure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun setAge(age: Int) {
require(age in 0..150) {
"Age out of range: $age"
}
}
fun main() {
// require failure throws IllegalArgumentException
setAge(30)
// check failure throws IllegalStateException:
check(true) { "State OK" }
// error() to fail explicitly:
val value: String? = "ok"
val v = value ?: error("Value is null")
println(v)
}
// assert is only enabled in debug:
// assert(false) { "Test only" }
// Enable assertions with -ea parameter

Nothing and Failure

Nothing represents never returning — for unimplemented (todo) or must-fail (error) paths. The type system understands the control flow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun main() {
val x = "a"
val result = when (x) {
"a" -> 1
else -> fail("Unknown input")
}
println(result) // 1
}
// Nothing is a subtype of any type:
fun nullable(): String? = fail("empty")
// Not-implemented placeholder:
fun notImplemented(): Nothing {
throw NotImplementedError("Not implemented yet")
}
// error() returns Nothing:
// val cfg = mapOf("port" to 8080)
// val port = cfg["port"] ?: error("Missing port")

12.Input and Output

File reading/writing, Path API, standard I/O, JSON serialization, and file operations.

Read File

readText reads the whole file as a string; readBytes reads bytes. Specify the charset to avoid mojibake.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.File
fun main() {
val text = File("data.txt").readText()
println(text)
// Specify charset:
val utf8 = File("data.txt")
.readText(Charsets.UTF_8)
println(utf8.length)
// Read bytes:
val bytes = File("image.png").readBytes()
println(bytes.size)
}
// Relative path is based on working directory
// File not found throws FileNotFoundException
// Wrap reads with runCatching for error handling

Write File

writeText overwrites; appendText appends; writeBytes writes bytes. The directory must exist first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.File
fun main() {
File("out.txt").writeText("Hello, Kotlin!")
// Append:
File("log.txt").appendText("new line\n")
// Write bytes:
File("data.bin").writeBytes(byteArrayOf(1, 2))
// Specify charset:
File("out.txt").writeText("Chinese content", Charsets.UTF_8)
// Multiple lines:
File("out.txt").writeText(
listOf("a", "b", "c").joinToString("\n")
)
}
// writeText overwrites existing content by default
// Missing directory throws IOException
// Use buffered streams for large files

Line-by-Line Reading

readLines reads all lines; useLines is a lazy stream for big files. forEachLine is a convenient traversal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.File
fun main() {
// All lines (small files):
val lines = File("data.txt").readLines()
lines.forEach { println(it) }
// Lazy line-by-line (large files):
File("big.log").useLines { seq ->
seq.filter { "ERROR" in it }
.take(5)
.forEach { println(it) }
}
}
// useLines automatically closes the file
// Don't use readLines to load huge files at once
// forEachLine is a convenient line iterator

Path API

java.nio.file.Path handles paths; Files provides read/write and directory operations. Path joining is cross-platform.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.nio.file.Files
import java.nio.file.Path
fun main() {
val base = Path.of("data")
val file = base.resolve("note.txt")
println(file) // data\note.txt
println(Files.exists(file))
println(file.fileName)
println(file.parent)
// Create directories:
Files.createDirectories(Path.of("a/b/c"))
// Copy and move:
Files.copy(file, Path.of("backup.txt"))
Files.move(file, Path.of("new.txt"))
}
// Path.of adapts path separator to the platform automatically
// Files.list must be closed with use
// Files.readString / writeString is simpler

Standard Input

readln reads a line; readlnOrNull reads possibly null. Convert manually to the desired type after reading.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
println("Enter name:")
val name = readln() // reads a line
println("Hello, $name")
// Read with possible empty input:
val line = readlnOrNull() ?: ""
// Read a number (convert manually):
val age = readln().toIntOrNull() ?: 0
println("Age: $age")
}
// Bulk line input:
// generateSequence { readlnOrNull() }
// .take(5).forEach { println(it) }
// readln throws on EOF, readlnOrNull is safer

Standard Output

println/print write to standard output; println adds a newline. Can also write to the error stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
println("hello") // with newline
print("world") // no newline
print("!\n")
val name = "Kotlin"
println("Hello, $name")
// Formatting:
println("%5.2f".format(3.14159)) // 3.14
// Output to error stream:
System.err.println("error message")
}
// println adds a newline every time
// Use BufferedWriter for large output
// PrintWriter is closer to Java style

JSON Serialization

kotlinx.serialization is the official serialization library; @Serializable generates codecs. Jackson is an alternative.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Dependency:
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class User(val name: String, val age: Int)
fun main() {
val json = Json { prettyPrint = true }
val u = User("Alice", 30)
val text = json.encodeToString(u)
println(text)
val back = json.decodeFromString<User>(text)
println(back)
}
// Need to enable the kotlin serialization plugin in Gradle
// Ignore unknown fields: Json { ignoreUnknownKeys = true }

File Operations

Check existence, delete, rename, get size and permissions. walkTopDown recursively walks directories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.File
fun main() {
val f = File("data.txt")
println(f.exists())
println(f.isFile)
println(f.isDirectory)
println(f.length()) // size in bytes
println(f.lastModified())
// Delete and rename:
f.delete()
f.renameTo(File("new.txt"))
// Create:
f.createNewFile()
// Temporary file:
val tmp = File.createTempFile("prefix", ".tmp")
tmp.deleteOnExit()
// Recursive walk:
File(".").walkTopDown()
.filter { it.extension == "kt" }
.forEach { println(it) }
}

13.Common Pitfalls

The most common pitfalls in everyday Kotlin development, with BAD/GOOD examples.

Overuse of !!

!! throws NPE on null — avoid it. Use safe call, Elvis, or eager validation instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
// BAD: dereferencing null directly crashes
// val len = maybe!!.length
// GOOD: Elvis provides a default
val len = maybe?.length ?: 0
// GOOD: validate early with clear errors
val text = maybe ?: error("config missing")
// GOOD: safe call then handle
maybe?.let { println(it.length) }
println(len)
println(text)
}
// !! only when logically guaranteed non-null
// Team review should block !! abuse

Smart Cast Failure

Smart casts fail on mutable or delegated properties. Re-check after assignment or capture into a local first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Box(var value: Any? = null)
// BAD: smart cast fails for var property
fun bad(b: Box) {
// println(b.value.length) compile error
}
// GOOD: copy to local val then check
fun good(b: Box) {
val v = b.value
if (v is String) {
println(v.length)
}
}
fun main() {
good(Box("hello"))
}
// Reason: var may be concurrently modified
// Delegated properties cannot be smart-cast either
// Prerequisite for smart cast: unchanged after the check

== vs ===

== compares structural equality; === compares reference identity. Distinguish between objects and primitives.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun main() {
val s1 = "abc"
val s2 = "abc"
// BAD: === on content depends on constant pool
println(s1 === s2) // may be true or false
// GOOD: use == for structural comparison
println(s1 == s2) // true
}
// BAD: regular class without overriding equals
class Point(val x: Int)
fun badEq() {
println(Point(1) == Point(1)) // false
}
// GOOD: data class auto generates equals
fun goodEq() {
println(Point2(1) == Point2(1)) // true
}
data class Point2(val x: Int)
// Numeric boxing equality:
// val a: Int? = 100
// val b: Int? = 100
// a == b // true, structural equality

Nullable Concatenation

When a nullable value is concatenated into a string template, null renders as "null". Handle null explicitly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val name: String? = null
// BAD: prints the literal "null"
println("Hello $name")
// GOOD: Elvis provides default
println("Hello ${name ?: "guest"}")
// GOOD: ?. then fallback
println("Hello ${name?.uppercase() ?: ""}")
}
// BAD: concatenation becomes null.toString
// val msg = "User: " + name // User: null
// GOOD:
// val msg = "User: ${name ?: "Unknown"}"
// Remember curly braces in template property access

Template Nullable Trap

$a.b is parsed as property access a.b. Nullable object property access needs ${a?.b}.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data class User(val nick: String?)
fun main() {
val u = User(null)
// BAD: $u.nick is parsed as property access
// println("$u.nick") syntax ambiguity
// GOOD: wrap expression in curly braces
println("${u.nick}") // null
// BAD: prints raw null, hard to read
println("Nickname: ${u.nick}")
// GOOD: Elvis fallback
println("Nickname: ${u.nick ?: "unset"}")
}
// Single variable template: $name is fine
// Dot access must use curly braces: ${u.nick}

Array Covariance Trap

Array<String> can be assigned to Array<Any>, risking ArrayStoreException at runtime. Prefer List.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
// BAD: array covariance can crash at runtime
val strings = arrayOf("a", "b")
val anys: Array<Any> = strings
// anys[0] = 1 throws ArrayStoreException
// GOOD: use List (safe covariance)
val list: List<Any> = listOf("a", "b")
println(list.size)
}
// GOOD: create new array instead of casting
// val safe = strings.map { it as Any }.toTypedArray()
// Reason: JVM arrays retain element type at runtime
// Kotlin keeps array covariance for Java interop
// Read-only List is immutable, no such issue

lateinit Uninitialized

Accessing a lateinit property before initialization throws UninitializedPropertyAccessException. Check isInitialized first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Service {
lateinit var client: String
fun start() {
client = "connected"
}
fun ping() {
// BAD: crashes if start() not called
// println(client)
// GOOD: check before access
if (::client.isInitialized) {
println(client)
}
}
}
fun main() {
val s = Service()
s.start()
s.ping()
}
// BAD: initializing with empty string masks the problem
// lateinit var s: String = ""
// GOOD: val + by lazy is safer
// Or use nullable type with null checks

Scope Function Confusion

let/apply/run have different return values. Misuse returns the receiver instead of the result. Choose by purpose.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data class User(var name: String)
fun main() {
val u = User("Alice")
// BAD: apply returns the object, not the result
val upper = u.apply {
name = name.uppercase()
}
println(upper.name) // ALICE, but upper is a User
// GOOD: let returns the lambda result
val upper2 = u.let { it.name.uppercase() }
println(upper2) // ALICE, String
}
// Selection rules:
// returns receiver -> apply / also
// returns result -> let / run / with
// need null check -> let + ?.

Companion Object Confusion

Companion object members aren't class-level static fields. Kotlin uses companion objects for static semantics; expose to Java with @JvmStatic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Config {
companion object {
val version = "1.0"
fun load() = println("loading")
}
}
fun main() {
// GOOD: access directly via class name in Kotlin
println(Config.version)
Config.load()
}
// BAD: assuming companion object members are static fields
// Java reflection cannot pick up static fields
// GOOD: @JvmStatic exposes them to Java
class Config2 {
companion object {
@JvmStatic
fun load() = println("loading")
}
}
// Companion is still an object instance
// Java access: Config.Companion.load()

Read-Only View Misunderstanding

The read-only List is just a view — the underlying mutable collection can still mutate. Copy when sharing mutable state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
// BAD: thinking read-only view means immutable
val mutable = mutableListOf(1, 2)
val view: List<Int> = mutable
mutable.add(3) // view also changes!
println(view) // [1, 2, 3]
// GOOD: immutable copy
val frozen = mutable.toList()
println(frozen)
// GOOD: use listOf for true immutability
val fixed = listOf(1, 2)
println(fixed)
}
// Sharing collections across threads:
// BAD: share a mutable collection
// GOOD: immutable collection or add a lock
// toList/toSet makes a new immutable copy

14.Concurrency and Coroutines

Threads, coroutines, async/await, Channel, Flow, mutex locks, and timeout control.

Thread Basics

Thread and thread{} create threads. join waits for completion. Synchronize when sharing mutable state in concurrency.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import kotlin.concurrent.thread
fun main() {
// Create and start a thread:
val t = thread(start = true) {
repeat(3) {
println("Worker thread: $it")
Thread.sleep(100)
}
}
t.join() // wait for the child thread to finish
println("Main thread done")
}
// Use ExecutorService to manage a thread pool:
// val pool = Executors.newFixedThreadPool(4)
// Shared mutable state across threads needs synchronization
// Thread.sleep is a blocking wait

Coroutine Basics

launch starts a lightweight coroutine; delay suspends without occupying a thread. runBlocking blocks until coroutines complete.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlinx.coroutines.*
fun main() = runBlocking {
// launch starts a coroutine (non-blocking):
launch {
delay(100) // suspends, doesn't block the thread
println("Coroutine A")
}
launch { println("Coroutine B") }
// runBlocking waits for all coroutines to complete
println("Sequential code reaches here")
}
// suspend functions can only be called inside coroutines
// delay suspends the coroutine, Thread.sleep blocks the thread
// Dependency: kotlinx-coroutines-core

async and await

async runs concurrently and returns Deferred; await gets the result. awaitAll waits for many tasks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlinx.coroutines.*
suspend fun fetch(id: Int): String {
delay(100)
return "data$id"
}
fun main() = runBlocking {
// async runs concurrently, await gets the result:
val a = async { fetch(1) }
val b = async { fetch(2) }
println(a.await() + b.await()) // data1data2
// Concurrent list:
val results = (1..3).map { id ->
async { fetch(id) }
}.awaitAll()
println(results)
}
// Deferred<T> is like Future
// If async fails, await rethrows the exception
// Use map + awaitAll to run many tasks concurrently

withContext

withContext switches dispatchers for blocking tasks. Use Dispatchers.IO for I/O; Default for compute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import kotlinx.coroutines.*
fun main() = runBlocking {
// Switch IO-heavy tasks to the IO dispatcher:
val data = withContext(Dispatchers.IO) {
Thread.sleep(10) // simulate blocking IO
"read from disk"
}
println(data)
// Default: CPU-intensive computing
val sum = withContext(Dispatchers.Default) {
(1..1000).sum()
}
println(sum)
}
// Dispatchers.IO: network/disk IO tasks
// Dispatchers.Default: compute-heavy tasks
// Dispatchers.Main: Android main thread
// withContext returns the value of the last expression

Channel

Channel transports data between coroutines; send/receive are suspending functions. Supports buffering and closing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
fun main() = runBlocking {
val channel = Channel<Int>(capacity = 5)
// Producer:
launch {
for (i in 1..3) {
channel.send(i)
println("sent $i")
}
channel.close()
}
// Consumer:
for (v in channel) {
println("received $v")
}
}
// Unbuffered: send and receive pair up synchronously
// Buffered: capacity specifies buffer size
// Iterating channel ends after it closes
// Use broadcast for multiple producers/consumers

Flow

Flow is an asynchronous cold stream — it runs at collection time. Supports map/filter operators and backpressure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun countDown(): Flow<Int> = flow {
for (i in 3 downTo 1) {
emit(i) // emit an element
delay(100)
}
}
fun main() = runBlocking {
countDown()
.map { "second $it" }
.onEach { println(it) }
.collect()
// flowOf / asFlow quickly create flows:
listOf(1, 2).asFlow().collect { println(it) }
}
// Flow is cold: only runs when collected
// Supports backpressure: emits at consumer rate
// collect is a suspend function

Mutex and Synchronization

AtomicInteger provides lock-free counters. Use Mutex inside coroutines to guard critical sections; synchronized for threads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import java.util.concurrent.atomic.AtomicInteger
// Shared counter:
val counter = AtomicInteger(0)
fun main() = runBlocking {
// Approach 1: atomic class
val jobs = List(10) {
launch { repeat(1000) { counter.incrementAndGet() } }
}
jobs.forEach { it.join() }
println(counter.get()) // 10000
// Approach 2: coroutine mutex
val mutex = Mutex()
var total = 0
launch {
repeat(1000) {
mutex.withLock { total++ } // critical section
}
}.join()
println(total)
}
// @Synchronized for ordinary thread methods
// volatile guarantees visibility, not atomicity
// Use Mutex inside coroutines, synchronized for threads

Timeout and Cancellation

withTimeout limits coroutine execution time. job.cancel is cooperative cancellation — check isActive.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import kotlinx.coroutines.*
fun main() = runBlocking {
// Timeout control:
try {
withTimeout(500) {
repeat(10) {
delay(200) // total 2 seconds
}
}
} catch (e: TimeoutCancellationException) {
println("timed out")
}
// Cancel a coroutine:
val job = launch {
while (isActive) {
println("working...")
delay(100)
}
}
delay(300)
job.cancel() // request cancellation
println("cancelled")
}
// Cooperative cancellation: must check isActive
// Suspension points respond to cancellation, throw CancellationException
// Clean up resources in finally + withContext(NonCancellable)

15.Network Programming

HTTP requests, Ktor Client, JSON parsing, TCP Sockets, HTTP servers, and WebSocket.

Simple URL Request

URL.readText is a quick GET. openConnection lets you configure timeouts. Good for small lightweight responses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.net.URL
fun main() {
// Simple GET request:
val html = URL("https://example.com")
.readText()
println(html.take(100))
// With timeouts:
val conn = URL("https://example.com")
.openConnection()
conn.connectTimeout = 3000
conn.readTimeout = 3000
val body = conn.getInputStream().readBytes()
println(body.size)
}
// readText suits small responses
// Use HttpClient in production for more control
// Remember to handle network exceptions

JDK HttpClient

JDK 11+ has built-in HttpClient for HTTP requests. Supports async sendAsync and response-body conversion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun main() {
val client = HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(5))
.build()
val request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.GET()
.build()
val response = client.send(
request, HttpResponse.BodyHandlers.ofString()
)
println(response.statusCode())
println(response.body().take(100))
}
// JDK 11+ built-in HTTP client
// Async version uses sendAsync returning CompletableFuture
// Supports GET/POST/headers/timeout configuration

Ktor Client

Ktor Client is a cross-platform HTTP library supporting coroutines and multiple engines. Good for network-heavy tasks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Dependencies: io.ktor:ktor-client-core
// io.ktor:ktor-client-java
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.runBlocking
suspend fun fetch() {
val client = HttpClient()
val body = client.get("https://example.com")
.bodyAsText()
println(body.take(100))
client.close()
}
fun main() = runBlocking {
fetch()
}
// Ktor Client: cross-platform HTTP library
// Supports WebSocket, SSE, auth
// Engine is swappable: OkHttp / CIO / Java

Parse Response JSON

After getting a response via HttpClient, use kotlinx.serialization to deserialize JSON into a data class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.decodeFromString
@Serializable
data class Repo(val name: String, val url: String)
fun main() {
val client = HttpClient.newBuilder().build()
val req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/repos/kotlin/kotlinx.serialization"))
.header("Accept", "application/json")
.GET()
.build()
val body = client.send(
req, HttpResponse.BodyHandlers.ofString()
).body()
// Parse JSON:
val repo = Json.decodeFromString<Repo>(body)
println(repo.name)
}
// First @Serializable then decodeFromString
// Use @SerialName for mismatched field names
// Ignore unknown fields: Json { ignoreUnknownKeys = true }

TCP Socket

ServerSocket listens on a port; accept accepts a connection. Socket reads/writes byte streams. Remember to close resources.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.net.ServerSocket
import java.net.Socket
fun main() {
// Server listens on port:
val server = ServerSocket(9999)
println("Listening on 9999...")
val client = server.accept() // blocks until a connection arrives
val input = client.getInputStream().bufferedReader()
println("Received: " + input.readLine())
client.close()
server.close()
}
// Client connects:
// val s = Socket("localhost", 9999)
// s.getOutputStream().write("hi".toByteArray())
// Production uses NIO / Netty for high concurrency
// Remember to close resources (use auto-closes)

HTTP Server

JDK's built-in HttpServer creates a lightweight HTTP service. createContext registers route handlers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.sun.net.httpserver.HttpServer
import java.net.InetSocketAddress
fun main() {
val server = HttpServer.create(
InetSocketAddress(8080), 0
)
server.createContext("/hello") { exchange ->
val body = "Hello, Kotlin!".toByteArray()
exchange.responseHeaders.add(
"Content-Type", "text/plain"
)
exchange.sendResponseHeaders(200, body.size.toLong())
exchange.responseBody.use { it.write(body) }
}
server.start()
println("Server started: http://localhost:8080/hello")
}
// Lightweight HTTP server built into the JDK
// Great for local tools and simple APIs
// Production recommends Ktor / Spring Boot
// Stop: server.stop(0)

REST Design Principles

REST expresses operations with resources and HTTP methods. data class describes request/response; status codes carry clear semantics.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class Todo(
val id: Int,
val title: String,
val done: Boolean = false,
)
// REST design notes:
// GET /todos list
// GET /todos/{id} detail
// POST /todos create (return 201)
// PUT /todos/{id} full update
// DELETE /todos/{id} delete (return 204)
fun main() {
val todos = listOf(Todo(1, "Learn Kotlin"))
println(Json.encodeToString(todos))
}
// Status codes should be meaningful
// Standardize error response structure
// Pagination params: page, size

WebSocket

OkHttp provides a WebSocket client with onOpen/onMessage callbacks for bidirectional messaging.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Dependency: com.squareup.okhttp3:okhttp
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
fun main() {
val client = OkHttpClient()
val request = Request.Builder()
.url("wss://echo.websocket.org")
.build()
val ws = client.newWebSocket(request,
object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
webSocket.send("hello")
}
override fun onMessage(webSocket: WebSocket, text: String) {
println("Received: $text")
}
override fun onClosing(ws: WebSocket, code: Int, reason: String) {
ws.close(code, reason)
}
})
Thread.sleep(2000)
ws.close(1000, "bye")
client.dispatcher.executorService.shutdown()
}
// WebSocket is full-duplex communication
// Reconnection must be implemented manually
// onMessage also receives ByteString binary

16.Date and Time

Instant, LocalDate, formatting, parsing, Duration, Period, and zoned date-times.

Current Timestamp

Instant.now records the current instant. currentTimeMillis gets milliseconds; nanoTime measures intervals.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.time.Instant
fun main() {
// Current instant:
val now = Instant.now()
println(now)
// Epoch seconds/milliseconds:
println(Instant.now().epochSecond)
println(Instant.now().toEpochMilli())
// Legacy millisecond timestamp:
println(System.currentTimeMillis())
println(System.nanoTime())
}
// Instant is an instant point, suitable for timestamps
// nanoTime for measuring intervals, not wall-clock time
// Distinguish UTC instants from local time

LocalDate

LocalDate represents year-month-day; create with of/now. Add/subtract days, compare, and access components.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDate
fun main() {
val today = LocalDate.now()
println(today)
val date = LocalDate.of(2026, 8, 2)
println(date)
// Arithmetic:
println(date.plusDays(1))
println(date.minusMonths(2))
println(date.plusWeeks(1))
// Queries:
println(today.isAfter(date))
println(today.dayOfWeek)
println(today.dayOfMonth)
println(today.month)
println(today.isLeapYear)
}
// LocalDate is date-only, no time, no zone
// Immutable, thread-safe

LocalDateTime

LocalDateTime has date and time. withHour adjusts a field; plusHours adds/subtracts hours.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.time.LocalDateTime
fun main() {
val now = LocalDateTime.now()
println(now)
val dt = LocalDateTime.of(2026, 8, 2, 15, 30)
println(dt.hour)
println(dt.minute)
println(dt.second)
// Adjust:
println(dt.withHour(9))
println(dt.plusHours(5))
// Convert with LocalDate:
println(dt.toLocalDate())
println(dt.toLocalTime())
}
// LocalDateTime is date+time, no time zone
// Field operations return new instances (immutable)

Formatting Output

DateTimeFormatter.ofPattern defines a custom format. yyyy-MM-dd HH:mm:ss is a common pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
fun main() {
val now = LocalDateTime.now()
// Common patterns:
println(now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
))
println(now.format(
DateTimeFormatter.ofPattern("yyyy/MM/dd")
))
println(now.format(
DateTimeFormatter.ofPattern("HH:mm")
))
// Built-in formatter:
println(now.format(DateTimeFormatter.ISO_LOCAL_DATE))
}
// Pattern letters: y year M month d day H hour m minute s second
// Formatter is thread-safe, can be reused as val
// Case-sensitive: M month, m minute

Parse Date

LocalDate.parse parses a string. Custom formats need a DateTimeFormatter. Failures throw exceptions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
fun main() {
// Parse ISO format:
println(LocalDate.parse("2026-08-02"))
// Custom format:
val fmt = DateTimeFormatter.ofPattern("yyyy/MM/dd")
println(LocalDate.parse("2026/08/02", fmt))
// Safe parse:
try {
LocalDate.parse("2026-13-40")
} catch (e: DateTimeParseException) {
println("Date format error: ${e.message}")
}
}
// Parse failure throws DateTimeParseException
// Validate before converting in batch parsing
// Wrap in try when input is untrusted

Duration

Duration is a nanosecond-based time amount; between computes the diff. Ideal for measuring elapsed time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.Duration
import java.time.Instant
fun main() {
val start = Instant.now()
// business work...
val end = Instant.now()
// Elapsed:
val elapsed = Duration.between(start, end)
println(elapsed.toMillis())
println(elapsed.toSeconds())
// Construct durations:
println(Duration.ofMinutes(5))
println(Duration.ofHours(2).plusMinutes(30))
// Compare:
println(elapsed.compareTo(Duration.ZERO))
}
// Duration is a nanosecond-based time amount
// Ideal for measuring elapsed time and differences
// ofMinutes/ofHours/ofDays are convenient constructors

Period

Period is a year/month/day amount; between computes age. ofWeeks/ofDays construct periods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.time.LocalDate
import java.time.Period
fun main() {
val born = LocalDate.of(1995, 5, 20)
val today = LocalDate.now()
// Year/month/day difference:
val age = Period.between(born, today)
println("${age.years} years ${age.months} months")
// Construct periods:
println(Period.of(1, 6, 0)) // 1 year 6 months
println(Period.ofDays(30))
// Apply to dates:
println(today.plus(Period.ofWeeks(2)))
}
// Period is date-based (years/months/days)
// Duration is time-based (hours/minutes/seconds)
// For pure day differences, use ChronoUnit.DAYS.between

Zoned Date-Time

ZonedDateTime carries a time zone. withZoneSameInstant switches zones. Store in UTC; display in local.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.time.ZonedDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
fun main() {
// Instant with time zone:
val now = ZonedDateTime.now()
println(now)
// Specify time zone:
val tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"))
println(tokyo)
// Switch time zone:
val ny = now.withZoneSameInstant(
ZoneId.of("America/New_York")
)
println(ny)
// Format with time zone:
println(now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
))
}
// ZonedDateTime = LocalDateTime + ZoneId
// Store as UTC (Instant), convert for display
// Daylight saving transitions are handled automatically

17.Processes and System

Command-line arguments, environment variables, ProcessBuilder for external commands, system info, and shutdown hooks.

Arguments and Environment

The main args receives command-line arguments. System.getenv reads env vars; getProperty reads system properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main(args: Array<String>) {
// Command-line arguments:
println("Argument count: ${args.size}")
args.forEachIndexed { i, a -> println("$i: $a") }
// Environment variables:
println(System.getenv("PATH"))
println(System.getenv().size)
// System properties:
println(System.getProperty("user.home"))
}
// Run: kotlin MainKt arg1 arg2
// Common properties: user.home / os.name / java.version
// Returns null when property doesn't exist

ProcessBuilder

ProcessBuilder runs external commands; directory sets the working directory. redirectErrorStream merges error streams.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.File
fun main() {
// Execute an external command:
val pb = ProcessBuilder("ls", "-l")
.directory(File("."))
.redirectErrorStream(true)
val process = pb.start()
// Wait and read output:
val output = process.inputStream.bufferedReader()
.readText()
println(output)
println("Exit code: ${process.waitFor()}")
}
// ProcessBuilder is safer than Runtime.exec
// redirectErrorStream merges error stream
// Pass command arguments separately, don't concat strings (prevent injection)

Exit Code

waitFor blocks waiting for the child process. Exit code 0 means success. exitProcess sets the current process's exit code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.File
fun main() {
// Start a child process:
val pb = ProcessBuilder("cmd", "/c", "echo hi")
val p = pb.start()
// Non-blocking check:
println("Started: ${p.isAlive}")
// Blocking wait:
val code = p.waitFor()
println("Exit code: $code")
}
// Exit code 0 means success
// exitProcess(1) exits the current process immediately
// Note: exitProcess skips finally cleanup

Capture Child Process Output

Read inputStream to capture child-process output. Use waitFor with timeout to avoid hangs; destroyForcibly force-kills.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.File
import java.util.concurrent.TimeUnit
fun main() {
// Capture stdout:
val p = ProcessBuilder("java", "-version")
.redirectErrorStream(true)
.start()
val out = p.inputStream.bufferedReader()
.use { it.readText() }
println("Output:\n$out")
// Wait with timeout:
if (p.waitFor(5, TimeUnit.SECONDS)) {
println("Exit code: ${p.exitValue()}")
} else {
p.destroyForcibly()
println("Timeout, killed")
}
}
// Read large output line by line to avoid memory bloat
// Prevent child blocking: read stream while waiting
// destroyForcibly kills the process

System Information

Read JVM and OS properties; Runtime exposes memory. Used for logging and diagnostics.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// JVM info:
println("Java: ${System.getProperty("java.version")}")
println("Vendor: ${System.getProperty("java.vendor")}")
println("OS: ${System.getProperty("os.name")}")
println("Arch: ${System.getProperty("os.arch")}")
// Memory:
val runtime = Runtime.getRuntime()
println("Max memory: ${runtime.maxMemory() / 1024 / 1024}MB")
println("Used memory: ${(runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024}MB")
// Working directory:
println("Working directory: ${System.getProperty("user.dir")}")
}
// Number of CPU cores:
// Runtime.getRuntime().availableProcessors()
// Production monitoring uses jcmd / JMX

Shutdown Hook

addShutdownHook registers a task that runs when the JVM exits. Used for cleanup and state persistence.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import kotlin.concurrent.thread
fun main() {
// Register a shutdown hook:
Runtime.getRuntime().addShutdownHook(
thread(start = false) {
println("Cleaning up resources...")
// Save state, close connections
}
)
// Main logic:
println("Program running")
Thread.sleep(2000)
println("Exit")
}
// Hooks run on normal JVM exit
// Not guaranteed in all forced exit scenarios
// kill -9 cannot trigger hooks

Real-Time Output Reading

Read the child-process output stream line by line to handle progress in real time. useLines auto-closes the stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.File
fun main() {
// Read child process output incrementally:
val p = ProcessBuilder(
"ping", "-n", "2", "127.0.0.1"
)
.redirectErrorStream(true)
.start()
// Stream lines in real time:
p.inputStream.bufferedReader().useLines { lines ->
lines.forEach { println("Progress: $it") }
}
println("Exit code: ${p.waitFor()}")
}
// Real-time reads prevent buffer overflow
// useLines auto-closes the input stream
// Line-by-line processing is memory-friendly for large data

Terminate Process

destroy is a graceful termination; destroyForcibly is a force kill. Force-terminate when timeout elapses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.File
import java.util.concurrent.TimeUnit
fun main() {
val p = ProcessBuilder(
"ping", "127.0.0.1", "-t"
)
.redirectErrorStream(true)
.start()
// Terminate after timeout:
val done = p.waitFor(3, TimeUnit.SECONDS)
if (!done) {
println("Timeout, terminating")
p.destroy() // graceful termination
p.waitFor()
println("Exit code: ${p.exitValue()}")
}
}
// destroy sends termination signal, child can respond
// destroyForcibly kills hard, child has no chance to clean up
// Windows uses taskkill /F to force-kill the process tree

18.Regular Expressions

Regex basics, match search, capture groups, replace/split, common patterns, and flag options.

Regex Basics

Regex creates a regex object. matches for full match; containsMatchIn to check containment; matchEntire for whole-string match.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
// Build a regex:
val email = Regex("^\\w+@\\w+\\.\\w+$")
println(email.matches("[email protected]")) // true
println(email.matches("not-an-email")) // false
// containsMatchIn checks inclusion:
println(Regex("\\d+").containsMatchIn("has 123 items"))
// matchEntire full match:
val num = Regex("-?\\d+\\.?\\d*")
println(num.matchEntire("3.14") != null)
}
// Regex is thread-safe, reuse as val
// Mind backslash escape: \\d means digit class
// Use raw strings """ to reduce escaping

Literal and Options

Regex.escape escapes special characters. IGNORE_CASE ignores case; MULTILINE is multiline mode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlin.text.RegexOption
fun main() {
// Literal match (special chars as-is):
val literal = Regex.escape("a.b*c")
println(literal) // a\\.b\\*c
println(Regex(literal).matches("a.b*c")) // true
// Case insensitive:
val ci = Regex("kotlin", RegexOption.IGNORE_CASE)
println(ci.matches("KOTLIN")) // true
// Combine options:
val multi = Regex("^a$", setOf(
RegexOption.MULTILINE,
RegexOption.IGNORE_CASE,
))
println(multi.matches("A"))
}
// Metacharacters: . * + ? ( ) [ ] { } ^ $ |
// Escape special chars first with Regex.escape
// Options: IGNORE_CASE / MULTILINE / DOT_MATCHES_ALL

Find Match

find returns the first match; findAll returns all matches. MatchResult exposes value, groups, and range.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val text = "id=42 and id=17 and id=8"
val re = Regex("id=(\\d+)")
// First match:
val first = re.find(text)
println(first?.value) // id=42
println(first?.groupValues) // [id=42, 42]
// All matches:
val all = re.findAll(text)
all.forEach { m -> println(m.value) }
// Range:
println(first?.range) // 0..4
}
// find returns the first MatchResult
// findAll returns a sequence (lazy)
// groupValues[0] is the whole, [1..] are groups

Capture Groups

Parentheses define capture groups. groupValues is indexed; named groups use groups["name"].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val text = "2026-08-02 15:30:00"
val re = Regex("(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})")
val m = re.find(text)!!
// Get group values:
println(m.groupValues[1]) // 2026
println(m.groupValues[2]) // 08
// Named groups:
val named = Regex("(?<year>\\d{4})-(?<month>\\d{2})")
val nm = named.find("2026-08")!!
println(nm.groups["year"]?.value) // 2026
println(nm.groups["month"]?.value) // 08
}
// Non-capturing group: (?:...)
// Lazy match: (.*?)
// Destructured match: val (y, m, d) = m.destructured

Regex Replace

replace replaces all matches; replaceFirst only the first. The replacement closure can access capture groups.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val text = "2026-08-02 15:30"
// Replace matched content:
println(Regex("\\d+").replace(text, "#"))
// Replace first:
println(Regex("\\d+").replaceFirst(text, "X"))
// Generate replacement from match:
val date = Regex("(\\d{4})-(\\d{2})-(\\d{2})")
println(date.replace(text) { m ->
val (y, mo, d) = m.destructured
"$d/$mo/$y"
})
}
// replace handles all matches
// Replacement closure can access group values
// m.destructured destructures the groups

Regex Split

split(Regex) splits a string with a regex. More flexible than string splitting; supports multiple delimiters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val text = "1, 2;3|4"
// Regex split:
println(text.split(Regex("[,;|]+")))
println(text.split(Regex("\\s*[,;|]\\s*")))
// Extract key-value pairs:
val log = "A:1 B:2 C:3"
println(Regex("(\\w+):(\\d+)").findAll(log)
.map { it.destructured.toList() }
.toList())
}
// split doesn't keep delimiters by default
// Regex split is more flexible than string split
// Use findAll for lazy processing of large text

Common Regex

Common regex templates for email, phone, IPv4, Chinese chars, and URL. Adjust boundaries as needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Email:
val EMAIL = Regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
// Phone number (mainland China):
val CN_PHONE = Regex("^1[3-9]\\d{9}$")
// IPv4:
val IPV4 = Regex("^(\\d{1,3}\\.){3}\\d{1,3}$")
// Chinese characters:
val CHINESE = Regex("[\\u4e00-\\u9fa5]")
// URL:
val URL = Regex("^https?://[^\\s]+")
fun main() {
println(EMAIL.matches("[email protected]"))
println(CN_PHONE.matches("13800138000"))
println(IPV4.matches("192.168.1.1"))
println(CHINESE.containsMatchIn("Hello world"))
println(URL.matches("https://example.com/x"))
}
// Complex rules suggest dedicated libraries or state machines
// Separate validation from extraction: match first then read groups
// Regex matching can pose ReDoS risks, mind input length

Flag Options

RegexOption constants control matching behavior. Inline flags (?i)(?m)(?s) can appear inside the pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import kotlin.text.RegexOption
fun main() {
// Option constants:
println(RegexOption.IGNORE_CASE) // ignore case
println(RegexOption.MULTILINE) // multiline mode
println(RegexOption.DOT_MATCHES_ALL) // . matches newline
// Inline flag:
val inline = Regex("(?i)abc")
println(inline.matches("ABC"))
// Combine usage:
val combined = Regex("^\\w+$", RegexOption.IGNORE_CASE)
println(combined.matches("Hello123"))
}
// (?i) ignore case (?m) multiline (?s) single-line
// Flags as parameters are clearer than inline
// COMMENT option allows whitespace and comments in the regex

19.Build and Dependencies

Gradle Kotlin DSL, plugins, dependencies, testing, coroutines, and serialization library configuration.

Gradle Basics

build.gradle.kts uses Kotlin DSL to configure the build. mainClass specifies the run entry point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// File: build.gradle.kts (Kotlin DSL)
plugins {
kotlin("jvm") version "2.0.21"
application
}
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
application {
mainClass.set("MainKt")
}
// Run: ./gradlew run
// Package: ./gradlew build
// Directory conventions:
// src/main/kotlin/ source
// src/test/kotlin/ tests
// build/libs/ artifacts

Kotlin Plugin

kotlin("jvm") declares a JVM project. jvmToolchain specifies the JDK version. Wrapper pins Gradle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Plugin declaration:
plugins {
kotlin("jvm") version "2.0.21"
}
// Common plugins:
// kotlin("jvm") JVM project
// kotlin("multiplatform") multiplatform
// kotlin("kapt") annotation processing
// Set JDK toolchain:
kotlin {
jvmToolchain(21)
}
// Pin Gradle version with Wrapper:
// ./gradlew wrapper --gradle-version 8.10
// Multi-module setups include in settings.gradle.kts

Dependency Configuration

implementation/testImplementation/compileOnly etc. control dependency scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dependencies {
// Runtime dependency:
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0"
)
// Test dependency:
testImplementation("junit:junit:4.13.2")
// Compile-time only dependency:
compileOnly(
"jakarta.servlet:jakarta.servlet-api:6.0.0"
)
}
// Dependency configurations explained:
// implementation visible at runtime and compile-time
// testImplementation test scope only
// compileOnly compile-time only (not packaged)
// runtimeOnly runtime only
// api exposed to consumers (library projects)

Run and Distribution

The application plugin provides run/installDist tasks. run --args passes CLI arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins {
application
}
application {
mainClass.set("MainKt")
}
// CLI runs:
// ./gradlew run run main class
// ./gradlew run --args="arg1 arg2"
// ./gradlew installDist generate distributable scripts
// Set JVM args:
tasks.named<JavaExec>("run") {
jvmArgs("-Xmx512m")
}
// Output location: build/install/<project>/bin/

Testing

kotlin.test provides assertions. testImplementation brings in test deps. The test task runs all tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import kotlin.test.Test
import kotlin.test.assertEquals
// Function under test (src/main/kotlin):
fun add(a: Int, b: Int) = a + b
// Test class (src/test/kotlin/CalculatorTest.kt):
class CalculatorTest {
@Test
fun add_returns_sum() {
assertEquals(5, add(2, 3))
}
}
// Run tests:
// ./gradlew test
// Common assertions:
// assertEquals / assertTrue / assertNull
// assertFailsWith<IllegalArgumentException> { ... }
// Test report: build/reports/tests/test/

Coroutine Dependencies

kotlinx-coroutines-core provides coroutine core. Android/Swing variants provide matching main-thread dispatchers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dependencies {
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0"
)
// Android or JVM Swing main thread:
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
)
}
// Modules:
// kotlinx-coroutines-core core (coroutines, Flow, Channel)
// kotlinx-coroutines-android Android Dispatchers.Main
// kotlinx-coroutines-swing Swing main thread
// Core usage:
import kotlinx.coroutines.*
fun demo() = runBlocking {
launch { delay(100); println("done") }
}
// Version must be compatible with Kotlin version

Serialization Dependencies

The kotlin plugin.serialization plugin plus kotlinx-serialization-json enables JSON encoding/decoding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
plugins {
kotlin("jvm") version "2.0.21"
kotlin("plugin.serialization") version "2.0.21"
}
dependencies {
implementation(
"org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3"
)
}
// Usage:
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class User(val name: String, val age: Int)
fun main() {
println(Json.encodeToString(User("A", 1)))
}
// Serialization plugin generates codecs automatically
// Need both the plugin and the runtime library
// Other formats: kotlinx-serialization-protobuf etc.

Packaging and Publishing

The distribution plugin produces distributable archives. publishToMavenLocal publishes to the local Maven repo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Build distributable packages:
plugins {
application
distribution
}
application {
mainClass.set("MainKt")
}
// Run:
// ./gradlew distTar produce tarball
// ./gradlew distZip produce zip archive
// ./gradlew installDist extract to build/install
// Output includes startup scripts and lib/ deps
// Publish to local Maven repository:
// ./gradlew publishToMavenLocal
// Configure maven-publish plugin:
plugins {
`maven-publish`
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}

Official Links

Direct links to the official docs and resources.

About this Cheatsheet

This page is a self-contained Kotlin 2.0 cheatsheet, covering about 80% of common usage of the language core and the most-used standard library in real projects. It leans on modern idioms: null safety (nullable types ?, safe call ?., Elvis ?:), data class and sealed class, scope functions, extension functions, and the coroutine + Flow async concurrency model. For authoritative references, see the official Kotlin documentation and Kotlin in Action. 19 sections each focus on one topic — from your first program through coroutines, null safety, and common pitfalls. Each section is split into 8–14 example-driven subsections (5–20 lines each) for about 160 topics. Code snippets are intentionally short and self-explanatory; comments are in Simplified Chinese. All processing happens in the browser — no uploads, no tracking. This page is part of GuruToolkit's free developer tool collection; the snippets are free to use with no warranty.

Version 2.1.0