Every keyword, operator, type, builtin call and stdlib member the current kotlinrs build recognizes, grouped into chapters and carrying a signature, a description and a runnable example. This page is generated from the language-server corpus (src/lsp.rs) by the gen-docs binary, so it stays in sync with what the runtime and editor tooling actually know about: keywords mirror lexer.rs and the parser.rs modifier table, operators mirror lexer::operator, types mirror ast::Type::from_name, builtins mirror the arms of compiler::compile_call, and each member mirrors an arm of host::kt_method, char_method, obj_method, sequence_member or coll_hof. Descriptions state what this runtime does; where the behaviour diverges from Kotlin proper, the entry says so.
Keywords & Declarations
# fun
fun name(p: T, …): R { … }
fun name(p: T, …): R = expr
Declares a function. Execution enters fun main, with or without an args: Array<String> parameter. A = expr body is a single-expression function. Only fun, class, object and interface may appear at the top level — kotlinrs has no top-level val/var.
fun cube(n: Int): Int = n * n * n
fun main() { println(cube(3)) } // 27
# val
val name: T = expr
Declares a read-only binding. The type annotation is optional and inferred from the initializer when it is absent.
val x = 41
println(x + 1) // 42
# var
var name: T = expr
Declares a reassignable binding. The compound assignments (+=, -=, *=, /=, %=) and ++/-- all write through a var.
var i = 0
i += 1
println(i) // 1
# if
if (cond) { … } else { … }
Conditional branch. It is also an expression whose value is the last statement of the branch taken; an if with no else that falls through evaluates to null.
val m = if (3 > 2) 3 else 2
println(m) // 3
# else
if (cond) { … } else { … }
when { … else -> … }
The fallback branch of an if, and the catch-all arm of a when. In a when it is terminal: arms written after it are unreachable and are not compiled at all.
println(if (4 % 2 == 0) "even" else "odd") // even
# while
while (cond) { … }
Loops while the condition stays true. kotlinrs has no do … while; the post-test loop is not part of the grammar.
var i = 0
while (i < 3) i += 1
println(i) // 3
# for
for (v in a..b) { … }
for (v in iterable) { … }
Iterates a range or an iterable value — a List, Set, Array, range, or String (by UTF-16 code unit). A syntactic range header compiles to a counted loop the JIT can trace; every other receiver goes through the host iterator. A Map is not iterable here and faults at run time.
for (i in 1..3) print(i) // 123
for (c in "ab") print(c) // ab
# in
for (v in iterable)
value in container
Two roles: the for header separator, and the membership operator over a range, List, Set, Array, a Map's keys, or a String's substrings. A when arm may also read in a..b.
println(2 in 1..3) // true
println("b" in "abc") // true
println("a" in mapOf("a" to 1)) // true
# return
return
return expr
Returns from the enclosing function. A bare return yields Unit. There is no labeled return@label for returning out of a lambda.
fun answer(): Int { return 42 }
fun main() { println(answer()) }
# until
a until b
Half-open ascending range: a until b includes a and excludes b. An infix function, so it binds looser than .. and than arithmetic.
for (i in 0 until 3) print(i) // 012
# downTo
a downTo b
Descending inclusive range, stepping by -1. Printing one shows the progression form, a downTo b step 1.
for (i in 3 downTo 1) print(i) // 321
# step
range step n
Re-steps a range into a progression. Its left operand is any range expression, not just a literal one, because step is an infix function at a looser precedence than ... A non-positive n raises IllegalArgumentException.
for (i in 0..10 step 5) print(i) // 0510
println(1..10 step 3) // 1..10 step 3
# when
when (subject) { cond -> … else -> … }
when { boolExpr -> … }
Multi-way branch, statement or expression. The subject form tests each arm against the subject with ==, in, or is; the subjectless form tests each arm as a Boolean. Arms are tried top to bottom and the first match wins; several conditions may share one arm, comma-separated. With no matching arm and no else the value is null.
val n = 5
println(when (n) { 1 -> "one"; in 2..9 -> "few"; else -> "many" }) // few
# is
value is Type
value !is Type
Runtime type check, in a when arm or in ordinary expression position. It compares the receiver's runtime class tag, so it recognizes the built-in kinds (Int, String, List, …) and user classes including inherited supertypes. A trailing ? on the type and a type-argument list are both accepted and ignored.
val x: Any = "s"
println(when (x) { is String -> "str"; is Int -> "int"; else -> "?" }) // str
# class
class Name(val p: T, …) : Super(args), Iface { … }
Declares a class. Only the primary constructor exists — there are no secondary constructors and no init block. A val/var primary-constructor parameter becomes a stored property; a plain parameter does not. The supertype list may name one class and any number of interfaces.
open class Animal(val name: String)
class Dog(n: String) : Animal(n)
fun main() { println(Dog("rex").name) } // rex
# interface
interface Name { fun m(): T; fun d(): T = expr }
Declares an interface. A member with no body is abstract; one with a body is a default implementation an implementor inherits. Interfaces cannot be instantiated. A soft keyword — interface is still usable as an ordinary identifier elsewhere.
interface Greeter {
fun greet(): String
fun loud(): String = greet().uppercase()
}
class En : Greeter { override fun greet(): String = "hi" }
fun main() { println(En().loud()) } // HI
# object
object Name { … }
Declares a singleton, constructed once before main runs and reachable by its own name. kotlinrs has no companion object and no anonymous object : T { } expression.
object Counter { var n = 0 }
fun main() { Counter.n = 5; println(Counter.n) } // 5
# data
data class Name(val p: T, …)
Marks a class as a data class, which generates equals, hashCode, toString, copy and componentN over the primary-constructor properties only. An inherited field is carried but is not part of the comparison and is skipped by componentN. A soft keyword — x.data is still a legal property read.
data class Pt(val x: Int, val y: Int)
fun main() { println(Pt(1, 2)) } // Pt(x=1, y=2)
# open
open class Name
open fun m(): T
Marks a class as extendable or a member as overridable, and it is enforced. Inheriting from a class that is not open is rejected with B is final, so it cannot be inherited from, and overriding a member that is not open with f in B is final and cannot be overridden .
open class Base { open fun f(): Int = 1 }
class D : Base() { override fun f(): Int = 2 }
fun main() { println(D().f()) } // 2
# override
override fun m(): T { … }
Replaces a supertype's member, and it is required — redeclaring a supertype member without it is rejected with f hides a member of supertype B and needs an override modifier , and writing it on a member that overrides nothing is rejected too. Dispatch is by the receiver's runtime class, resolved at the call site against every instantiable implementor; a single candidate compiles to a direct call with no test.
open class Base { open fun f(): Int = 1 }
class D : Base() { override fun f(): Int = 2 }
fun main() { val b: Base = D(); println(b.f()) } // 2
# abstract
abstract class Name
abstract fun m(): T
Declares a class that cannot be constructed, or a member with no body that subtypes must supply. Constructing one is a compile error: cannot construct abstract class Name.
abstract class Shape { abstract fun area(): Double }
class Sq(val s: Double) : Shape() { override fun area(): Double = s * s }
fun main() { println(Sq(3.0).area()) } // 9.0
# sealed
sealed class Name
Declares a sealed class — abstract, and conventionally the root of a closed set of subtypes matched by is arms. kotlinrs treats it exactly as abstract: it does not check that a when over the subtypes is exhaustive, so an unmatched subject still falls through to null.
sealed class Expr
data class Num(val v: Int) : Expr()
fun main() { val e: Expr = Num(3); println(when (e) { is Num -> e.v; else -> 0 }) } // 3
# final
final fun m(): T
Accepted and discarded — it restates the default. A class or member that is not marked open is already final, and the compiler enforces that, so writing final changes nothing.
open class B { open fun f(): Int = 1 }
class C : B() { final override fun f(): Int = 2 }
fun main() { println(C().f()) } // 2
# private
private fun m()
private class C
A visibility modifier. public, private, internal and protected are all parsed and then discarded — a single-file program has no visibility boundary to enforce, so every declaration is reachable from every other one.
private class Box(val v: Int)
fun main() { println(Box(7).v) } // 7
# inner
inner class C
Accepted and discarded. kotlinrs has no nested classes at all — every class is top-level — so the modifier never changes what is compiled.
class Holder(val v: Int)
fun main() { println(Holder(1).v) } // 1
# super
super.m(args)
super<T>.m(args)
Calls the supertype's implementation rather than the overriding one, resolved statically. The unqualified form walks the linearized ancestry for the nearest supertype that implements the member; super<T> names the supertype explicitly, and T must be a direct one.
open class B { open fun f(): Int = 1 }
class D : B() { override fun f(): Int = super.f() + 1 }
fun main() { println(D().f()) } // 2
# this
this
this.property
The receiver inside a class method or a property initializer. A bare name that matches a property of the enclosing class resolves to this.name implicitly, so writing this is optional. There is no this@Label.
class P(val x: Int) { fun twice(): Int = this.x * 2 }
fun main() { println(P(4).twice()) } // 8
# it
{ it }
The implicit single parameter of a lambda written with no parameter list. A lambda with an explicit list ({ a, b -> … }) has no it. Its type is unknown to the compiler, so it + 1 lowers to a native op that stays on the fast path when the value turns out to be a number and is handed back to the runtime when it turns out to be a Char — which is what keeps { it + 1 } over a List<Char> correct.
println(listOf(1, 2, 3).map { it * 2 }) // [2, 4, 6]
# break
break
break@label
Exits the enclosing loop. break@label exits the loop carrying that label, which is written label@ before the for/while.
for (i in 1..9) { if (i == 3) break; print(i) } // 12
# continue
continue
continue@label
Skips to the enclosing loop's next iteration. continue@label targets a labeled loop, which is how an inner loop advances an outer one.
outer@ for (i in 1..2) { for (j in 1..3) { if (j == 2) continue@outer; print("$i$j") } } // 1121
# try
try { … } catch (e: T) { … } finally { … }
Guarded block, and an expression: its value is the last statement of the body, or of the handler that ran. While an exception is unwinding, println/print are suppressed so nothing is emitted between the throw and its handler.
val n = try { 1 / 0 } catch (e: ArithmeticException) { -1 }
println(n) // -1
# catch
catch (name: Type) { … }
A handler arm. The first arm whose type the in-flight throwable is an instance of wins, walking the modelled JVM parent chain — so catch (e: Exception) catches an IllegalArgumentException. catch (e: Throwable) catches anything.
try { "x".substring(9) } catch (e: Exception) { println(e.message) }
# finally
finally { … }
Cleanup block, run on the normal and the exceptional path alike. Its own value is discarded — the try expression's value still comes from the body or the handler.
try { println("work") } finally { println("done") }
# throw
throw expr
Raises a throwable. Kotlin types it Nothing, so it is an expression and may appear on the right of ?: or as the body of a when arm. An uncaught throw prints Exception in thread "main" <fqn>: <message> and exits non-zero.
val x: Int? = null
val v = x ?: throw IllegalStateException("missing")
// Exception in thread "main" java.lang.IllegalStateException: missing
# null
null
The null reference, carried internally as the VM's undefined value. It prints and interpolates as null. Nullability is not checked statically: T? is parsed and discarded, so a null only makes itself known through ?., ?:, !!, or a runtime fault.
val x: Int? = null
println(x ?: 0) // 0
# true
true
The Boolean true literal. && and || short-circuit on it; a lambda predicate is only treated as satisfied when it returns exactly true (a null or non-Boolean result counts as false).
val ok: Boolean = true
println(ok) // true
# false
false
The Boolean false literal.
val done: Boolean = false
println(!done) // true
# import
import a.b.c
import a.b.*
import a.b.c as alias
Records an import. It matters for exactly one thing: kotlin.math names are unresolvable without it, matching Kotlin. A star import opens the whole package; a single-name import opens only that name; an as alias *replaces* the original spelling, so after import kotlin.math.abs as absolute the name abs is no longer in scope.
import kotlin.math.*
fun main() { println(sqrt(9.0)) } // 3.0
# package
package a.b
Accepted and discarded. A kotlinrs program is a single file with no package-level name resolution, so the declaration exists only so real Kotlin source parses.
package demo
fun main() { println("ok") }
# as
import a.b.c as alias
The import-renaming keyword — and only that. kotlinrs has no as cast operator and no as?: x as Int is a parse error. Use is for a runtime type check.
import kotlin.math.abs as absolute
fun main() { println(absolute(-4)) } // 4
# rust
rust { … }
An inline Rust FFI block, recognized before lexing and rewritten in place into a __rust_compile("<base64>", line) call. The block must sit inside a function body. Its #[no_mangle] pub extern exports become callable barewords from Kotlin, dispatched by name at run time.
fun main() {
rust { #[no_mangle] pub extern "C" fn twice(n: i64) -> i64 { n * 2 } }
println(twice(21)) // 42
}
# companion
class C { companion object { val K = 7; fun of(…): C = … } }
Declares the class's singleton companion. Its properties and functions are reached through the class name (C.K, C.of(…)) and, from inside the class, without any qualifier. One per class; a named companion is reached the same way.
class C { companion object { val K = 7 } }
println(C.K) // 7
# vararg
fun f(vararg xs: T)
Collects the call's trailing positional arguments into an array of the declared element type, which the body iterates or measures like any array. Supported as the last parameter.
fun total(vararg xs: Int): Int { var t = 0; for (x in xs) t += x; return t }
println(total(1, 2, 3)) // 6
# by
val name: T by lazy { … }
Property delegation, on a top-level property, a class property or a local val. Only by lazy is supported: the block runs at the FIRST read and its value is cached, so an initializer with an effect fires at use rather than at startup. lazy requires val; any other delegate is a compile error.
val z: Int by lazy { println("forcing"); 42 }
fun main() { println("before"); println(z); println(z) }
Operators
a + b
Addition on numbers, concatenation on String, and code-unit displacement on Char ('A' + 1 is 'B'). Two Int operands wrap on overflow; a Double operand makes the result Double. Its method spelling is plus.
println(1 + 2) // 3
println("a" + "b") // ab
println('A' + 1) // B
a - b
-a
Subtraction, and unary negation. Char - Char is the Int distance between the code units; Char - Int is a Char. Its method spelling is minus.
println(5 - 2) // 3
println('c' - 'a') // 2
a * b
Multiplication, wrapping for two Int operands. Its method spelling is times. There is no String * Int repeat operator — use String.repeat.
println(6 * 7) // 42
a / b
Division. Two integral operands divide truncating toward zero; a Double operand switches the whole expression to IEEE-754 division. Integer division by zero raises ArithmeticException: / by zero; Double division by zero yields Infinity.
println(7 / 2) // 3
println(7 / 2.0) // 3.5
a % b
Remainder, taking the dividend's sign for integers (-7 % 2 is -1). Integer % by zero raises ArithmeticException. Its method spelling is rem.
println(7 % 3) // 1
println(-7 % 3) // -1
name = value
recv.prop = value
recv[i] = value
Assignment to a var, to a property, or to an indexed slot. An indexed write into a Map inserts the key when it is absent; into a List or Array it is bounds-checked and raises IndexOutOfBoundsException past the end.
var i = 1
i = 2
val m = mutableMapOf<String, Int>()
m["a"] = 1
println("$i ${m["a"]}") // 2 1
# +=
target += value
Compound add-assign. It works on a var, a property, and an indexed slot alike, and reuses the + semantics — so it concatenates on a String target.
var s = "a"
s += "b"
println(s) // ab
# -=
target -= value
Compound subtract-assign, on a var, a property, or an indexed slot.
var n = 5
n -= 2
println(n) // 3
# *=
target *= value
Compound multiply-assign.
var n = 6
n *= 7
println(n) // 42
# /=
target /= value
Compound divide-assign, truncating when both sides are integral.
var n = 9
n /= 2
println(n) // 4
# %=
target %= value
Compound remainder-assign.
var n = 9
n %= 4
println(n) // 1
# ++
x++
++x
Increment by one. The postfix form's value is the target *before* the update, the prefix form's is the value *after* — and both work in expression position, not only as a statement. The target may be a variable, a property, or an indexed element.
var k = 0
println(k++) // 0
println(++k) // 2
# --
x--
--x
Decrement by one, with the same prefix/postfix value rule as ++.
var k = 2
println(k--) // 2
println(k) // 1
# ==
a == b
Structural equality. It compares numbers by value across Int/Double, Lists element-wise, Sets order-insensitively, Maps entry-wise, and data-class instances over their primary-constructor properties. An Array inherits identity equality, so arrayOf(1) == arrayOf(1) is false. Contrast ===.
println(listOf(1, 2) == listOf(1, 2)) // true
println(setOf(1, 2) == setOf(2, 1)) // true
# !=
a != b
The negation of ==, with the same structural rules.
println(1 != 2) // true
# ===
a === b
Referential identity: whether both sides denote the SAME object, which == does not ask. Two independently built collections are == and not ===, and a data class's generated equals changes only the former. Values this runtime does not box — numbers, Char, Boolean, String, null — compare by value, so 1 === 1 and "x" === "x" are true as they are on the JVM's interned literals. Two boxing artifacts are NOT modelled: an Any-typed integer outside the JVM's Integer cache, and a String assembled at run time, both of which the reference toolchain answers false.
println(listOf(1) === listOf(1)) // false
println(listOf(1) == listOf(1)) // true
val a = mutableListOf(1)
println(a === a) // true
# !==
a !== b
The negation of === — true when the two sides are different objects.
println(listOf(1) !== listOf(1)) // true
a < b
Less-than. Numbers compare numerically and Chars by code unit. Strings do not compare with the relational operators here — kotlinrs implements no compareTo on String.
println(1 < 2) // true
println('a' < 'z') // true
a > b
Greater-than, over numbers and Chars.
println(3 > 2) // true
# <=
a <= b
Less-than-or-equal, over numbers and Chars.
println(2 <= 2) // true
# >=
a >= b
Greater-than-or-equal, over numbers and Chars.
println(2 >= 3) // false
# &&
a && b
Short-circuiting logical AND: the right operand is not evaluated when the left is false.
val xs = listOf(1)
println(xs.isNotEmpty() && xs[0] == 1) // true
# ||
a || b
Short-circuiting logical OR: the right operand is not evaluated when the left is true.
println(false || 1 < 2) // true
!a
Logical negation. Written twice as a postfix it is instead the not-null assertion !!, and followed by =, in or is it forms !=, !in, !is.
println(!(1 > 2)) // true
# ..
a..b
Inclusive ascending range. It is a value, not only a loop header: it can be bound, passed, iterated, and asked for sum, first, last, reversed and the higher-order collection functions. 'a'..'z' builds a Char range.
val r = 1..5
println(r.sum()) // 15
# ?.
recv?.member
recv?.method(args)
Safe call. The receiver is evaluated once; when it is null the whole expression is null and the member is never dispatched. Chains short-circuit as a unit.
val s: String? = null
println(s?.length) // null
# ?:
a ?: b
Elvis. Yields the left operand unless it is null, in which case it evaluates and yields the right. Right-associative, binding looser than arithmetic and tighter than the comparisons — and its right side may be a throw.
val x: Int? = null
println(x ?: 0) // 0
# ::
::fn
Type::member
receiver::member
Callable reference: names a function as a VALUE. With no receiver it is a top-level function or a primary constructor; with a TYPE receiver it is unbound, so the resulting function takes the receiver as its first parameter; with any other expression it is bound, and that receiver is evaluated once where the reference is written. Type::class is not supported.
fun inc(x: Int) = x + 1
println(listOf(1, 2).map(::inc)) // [2, 3]
println(listOf("aa", "b").map(String::length)) // [2, 1]
# !!
expr!!
Not-null assertion: yields the operand, or raises NullPointerException when it is null. Lexed as two consecutive ! tokens, so a != b is unaffected.
val x: Int? = 1
println(x!! + 1) // 2
T?
Marks a type nullable. kotlinrs parses the mark and then discards it — there is no static null checking, so a nullable and a non-nullable annotation compile identically and a null only surfaces at run time.
val x: Int? = null
println(x) // null
# []
recv[index]
Indexed read. On a String the index is a UTF-16 code unit offset and the result is a Char; on a List, Set or Array it is a bounds-checked position; on a Map it is a key lookup that yields null when absent. Chainable: m[k][i].
println("abc"[1]) // b
println(listOf(10, 20)[1]) // 20
println(mapOf("a" to 1)["z"]) // null
# []=
recv[index] = value
Indexed write. A List or Array slot is bounds-checked and raises IndexOutOfBoundsException past the end; a Map key is inserted when absent. Compound forms (xs[0] += 1) go through the same path.
val xs = mutableListOf(1, 2)
xs[0] = 9
println(xs) // [9, 2]
# ->
{ params -> body }
cond -> result
Two roles: it separates a lambda's parameters from its body, and a when arm's conditions from its result. It also appears in a function type annotation, (Int) -> Int, whose parameter and return types are parsed and discarded.
val f: (Int) -> Int = { n -> n * 2 }
println(f(21)) // 42
label@ for (…) { … }
break@label
Loop labels. A label@ prefix names the loop that follows, and break@label / continue@label target it. There is no this@Label and no return@label.
outer@ for (i in 1..3) { for (j in 1..3) { if (j == 2) continue@outer; print(i) } } // 123
"text $name text ${expr}"
String template interpolation. A bare $name splices an identifier; ${…} splices an arbitrary expression, re-parsed from the source between the braces. Each interpolated value is rendered by the same stringifier println uses, so a Double keeps its .0 and null reads as null. \$ escapes a literal dollar.
val x = 2
println("x=$x sq=${x * x}") // x=2 sq=4
# to
first to second
The infix Pair constructor — the only way to build a Pair here, since Pair(a, b) is not a resolvable constructor. It is what mapOf takes as each argument.
val p = 1 to "one"
println(p.first) // 1
# !in
value !in container
Negated membership, over the same containers in accepts. A when arm may also read !in a..b.
println(4 !in 1..3) // true
# !is
value !is Type
Negated runtime type check, usable in a when arm and in ordinary expression position.
val x: Any = 1
println(x !is String) // true
recv.member
recv.method(args)
Member access. kotlinrs does not distinguish a property from a zero-argument method: both resolve through the same dispatch, so xs.size and xs.size() — and xs.sum and xs.sum() — are the same call. Chains are left-associative and bind tighter than the prefix unary operators.
println(listOf(1, 2, 3).size) // 3
name: Type
class C : Super(), Iface
Two roles: the type annotation separator on a binding, parameter or return type, and the supertype-list introducer on a class. Any identifier is accepted as a type name; only the eight primitives resolve to a static type, and every other name is carried as an opaque class name for dispatch.
val n: Int = 1
println(n) // 1
stmt; stmt
The optional statement separator. Newlines already terminate statements, so ; matters only when two statements share a line — including inside a when arm list written on one line.
var a = 1; a += 1; println(a) // 2
# as
value as T
value as? T
A checked cast. The runtime value is unchanged — what the cast supplies is the static type T, which then decides integer width and / dispatch downstream. A mismatch throws ClassCastException; the safe form as? yields null instead. Int and Long share one runtime representation here, so a cast cannot tell them apart.
val a: Any = 5
println((a as Int) / 2) // 2
println(a as? String) // null
Types
# Int
Int
Signed 32-bit integer. Values are carried in a 64-bit slot at run time, so every Int arithmetic result is narrowed back to 32 bits at the point it is produced: Int.MAX_VALUE + 1 wraps to Int.MIN_VALUE, exactly as on the JVM. / and % truncate toward zero.
val n: Int = 7 / 2
println(n) // 3
println(Int.MAX_VALUE + 1) // -2147483648
# Long
Long
Signed 64-bit integer, sharing Int's division rules. An L literal suffix marks a value as 64-bit and prints without it, so println(10L) writes 10. A Long operand keeps the whole expression at 64 bits, so it is not narrowed the way an Int result is: 2147483647L + 1L is 2147483648.
val big: Long = 10L
println(big) // 10
println(2147483647L + 1L) // 2147483648
# Double
Double
IEEE-754 double. It stringifies the way the JVM does: a whole value keeps a trailing .0, magnitudes outside [1e-3, 1e7) switch to scientific form (2.5E7), and the non-finite values print as NaN / Infinity / -Infinity.
println(3.0) // 3.0
println(1.0 / 0.0) // Infinity
# Float
Float
Accepted as an annotation and as an f-suffixed literal, but there is no distinct single-precision type: Type::from_name folds Float into Double, so a Float is stored, computed and printed at double precision.
val f: Float = 1.5f
println(f) // 1.5
# Boolean
Boolean
The true/false type, printed as true or false. A lambda predicate must return one: any other result — including null — is treated as not satisfied.
val b: Boolean = 1 < 2
println(b) // true
# Char
Char
A single UTF-16 code unit, and a distinct type from Int — it is carried as a tagged handle rather than a number, which is what makes it print as its character inside a List or a Map. Supports +/- displacement, comparison, .code, and 'a'..'z' ranges.
val c: Char = 'A'
println(c + 1) // B
println(listOf(c)) // [A]
# String
String
Text, with + concatenation and "$x" interpolation. Every length, index and slice position is a UTF-16 code-unit offset, matching the JVM contract rather than the Unicode scalar count. Indexing yields a Char; iterating a String walks its code units. String *literals* are read one source byte at a time, so a non-ASCII character written inside "…" splits into its UTF-8 bytes — a Char literal ('é') decodes correctly, a string literal does not.
val s: String = "n = ${1 + 1}"
println(s) // n = 2
# Unit
Unit
The no-value type — the result of a function with no return and of println, forEach and the other effect-only calls. The compiler renders it statically as the literal kotlin.Unit.
fun log(): Unit { println("hi") }
fun main() { log() }
# Any
Any
The top type. It resolves to no static type here, so it behaves as an unannotated binding: members dispatch dynamically on the runtime value and is decides what it actually holds.
val x: Any = "s"
println(x is String) // true
# List
List<T>
An ordered sequence, built by listOf. kotlinrs does not enforce read-only-ness — List and MutableList are the same runtime object, so listOf(1, 2).add(3) succeeds here where Kotlin rejects it at compile time. Prints as [a, b].
val xs: List<Int> = listOf(1, 2, 3)
println(xs) // [1, 2, 3]
# MutableList
MutableList<T>
The mutable List annotation, built by mutableListOf / arrayListOf. It denotes the same runtime object as List; the distinction is documentation only.
val xs: MutableList<Int> = mutableListOf(1)
xs.add(2)
println(xs) // [1, 2]
# Set
Set<T>
A distinct-element collection built by setOf. It keeps insertion order for display but compares order-insensitively, so setOf(1, 2) == setOf(2, 1) and a Set never equals a List. Prints as [a, b].
println(setOf(3, 1, 3)) // [3, 1]
# MutableSet
MutableSet<T>
The mutable Set annotation, built by mutableSetOf. add answers whether the element was new, which is what distinguishes it from a list's add.
val s: MutableSet<Int> = mutableSetOf(1)
println(s.add(1)) // false
# Map
Map<K, V>
A key/value association built by mapOf from k to v pairs. It is an insertion-ordered entry list, not a hash table — lookup is a linear scan under structural key equality, and iteration order is always insertion order even for hashMapOf. Prints as {k=v, k=v}.
val m: Map<String, Int> = mapOf("a" to 1)
println(m) // {a=1}
# MutableMap
MutableMap<K, V>
The mutable Map annotation, built by mutableMapOf / hashMapOf. put, remove and indexed assignment all write through it.
val m: MutableMap<String, Int> = mutableMapOf()
m["a"] = 1
println(m) // {a=1}
# Pair
Pair<A, B>
A two-element tuple, built only by the infix to — Pair(a, b) is not a resolvable constructor here. Its members are first and second, and it destructures through component1/component2.
val p: Pair<Int, String> = 1 to "one"
println(p.second) // one
# Array
Array<T>
A JVM-style array. It inherits identity equality and the JVM's descriptor toString, so arrayOf(1, 2) prints as [Ljava.lang.Integer;@0 rather than showing its elements — use joinToString or toList to see them.
val a: Array<Int> = arrayOf(1, 2, 3)
println(a[1]) // 2
println(a.joinToString()) // 1, 2, 3
# IntArray
IntArray
A primitive Int array, descriptor [I. DoubleArray ([D), BooleanArray ([Z) and CharArray ([C) are the other three. All four share the sequence members with List.
val a: IntArray = IntArray(3)
println(a.sum()) // 0
# IntRange
IntRange
The value a..b builds — iterable, summable, and a receiver for the higher-order collection functions. A re-stepped or reversed range becomes an IntProgression, which prints in its a..b step n / a downTo b step n form.
val r: IntRange = 1..5
println(r.sum()) // 15
println(r.reversed()) // 5 downTo 1 step 1
# Nothing
Nothing
The bottom type, the static type Kotlin gives a throw. kotlinrs accepts the annotation but resolves no static type from it — like every non-primitive name it is carried as an opaque class name.
fun fail(): Nothing = throw IllegalStateException("no")
fun main() { println(try { fail() } catch (e: Exception) { "caught" }) }
Builtin Functions
# println
println()
println(value: Any?)
Writes a value to stdout followed by a newline, and returns Unit. It takes at most one argument. While an exception is unwinding it is suppressed, so nothing is emitted between a throw and its handler.
println(6 * 7) // 42
# print
print()
print(value: Any?)
Writes a value to stdout with no trailing newline. Same one-argument limit and unwinding suppression as println.
print("a"); print("b") // ab
# listOf
listOf(vararg elements: T): List<T>
Builds a List from its arguments. The result is a plain mutable heap list — kotlinrs does not enforce read-only-ness, so add on it succeeds.
val xs = listOf(1, 2, 3)
println(xs.size) // 3
# mutableListOf
mutableListOf(vararg elements: T): MutableList<T>
Builds a mutable List. Identical to listOf at run time; the two differ only in what the annotation documents.
val xs = mutableListOf(1)
xs.add(2)
println(xs) // [1, 2]
# arrayListOf
arrayListOf(vararg elements: T): MutableList<T>
The java.util.ArrayList spelling of mutableListOf. It builds the same heap list.
println(arrayListOf(1, 2)) // [1, 2]
# emptyList
emptyList(): List<T>
Builds an empty List. An explicit type argument (emptyList<Int>()) is accepted and ignored — typing here is coarse.
println(emptyList<Int>()) // []
# setOf
setOf(vararg elements: T): Set<T>
Builds a Set: duplicates are dropped on the way in, insertion order is kept for display, and equality is order-insensitive. This is Kotlin's LinkedHashSet-backed behaviour.
println(setOf(3, 1, 3)) // [3, 1]
# mutableSetOf
mutableSetOf(vararg elements: T): MutableSet<T>
Builds a mutable Set. add on it answers whether the element was new.
val s = mutableSetOf(1)
println(s.add(2)) // true
# hashSetOf
hashSetOf(vararg elements: T): MutableSet<T>
Builds a java.util.HashSet, which iterates its BUCKET TABLE rather than its insertion sequence — so the printed order is neither the argument order nor a sorted one. A key sits in bucket (n - 1) and (h xor (h ushr 16)) of a power-of-two table sized from the element count, and buckets are walked in index order. Use linkedSetOf for insertion order or sortedSetOf for ascending order.
println(hashSetOf(3, 1)) // [1, 3]
# linkedSetOf
linkedSetOf(vararg elements: T): MutableSet<T>
The LinkedHashSet spelling. Insertion-ordered, which is what every Set here already is.
println(linkedSetOf(2, 1)) // [2, 1]
# sortedSetOf
sortedSetOf(vararg elements: T): MutableSet<T>
Builds a TreeSet — the elements in ascending natural order, whatever order they were given in, and kept that way as more are added.
println(sortedSetOf(3, 1, 2)) // [1, 2, 3]
# emptySet
emptySet(): Set<T>
Builds an empty Set.
println(emptySet<Int>()) // []
# mapOf
mapOf(vararg pairs: Pair<K, V>): Map<K, V>
Builds a Map from k to v pairs. Entries stay in insertion order and keys are matched by structural equality on a linear scan.
val m = mapOf("a" to 1, "b" to 2)
println(m["a"]) // 1
# mutableMapOf
mutableMapOf(vararg pairs: Pair<K, V>): MutableMap<K, V>
Builds a mutable Map. put, remove and m[k] = v all write through it.
val m = mutableMapOf("a" to 1)
m["b"] = 2
println(m) // {a=1, b=2}
# hashMapOf
hashMapOf(vararg pairs: Pair<K, V>): MutableMap<K, V>
Builds a java.util.HashMap, which iterates its BUCKET TABLE rather than its insertion sequence — the printed order follows the keys' hashes, not the argument order. Use mutableMapOf/LinkedHashMap when insertion order matters.
println(hashMapOf("b" to 1, "a" to 2)) // {a=2, b=1}
# HashMap
HashMap(): MutableMap<K, V> / HashMap(other: Map<K, V>): MutableMap<K, V>
The java.util.HashMap constructor — empty, or a copy of another map. It iterates in bucket order like hashMapOf, but a no-argument one starts from Java's default 16-bucket table where a sized builder starts from a table derived from its element count; the two mask differently, so the same keys can print in different orders.
val m = HashMap<String, Int>()
m["a"] = 1
println(m) // {a=1}
# LinkedHashMap
LinkedHashMap(): MutableMap<K, V> / LinkedHashMap(other: Map<K, V>): MutableMap<K, V>
The insertion-ordered map constructor — the same collection mapOf/mutableMapOf build.
println(LinkedHashMap(mapOf("b" to 2))) // {b=2}
# HashSet
HashSet(): MutableSet<T> / HashSet(other: Collection<T>): MutableSet<T>
The java.util.HashSet constructor — empty, or a copy of another collection's elements. Iterates in bucket order, like hashSetOf. Note the difference from the builder: HashSet(listOf(1)) copies the list, where hashSetOf(listOf(1)) builds a one-element set holding it.
println(HashSet(listOf(3, 1))) // [1, 3]
# LinkedHashSet
LinkedHashSet(): MutableSet<T> / LinkedHashSet(other: Collection<T>): MutableSet<T>
The insertion-ordered set constructor — the same collection setOf/linkedSetOf build.
println(LinkedHashSet(listOf(3, 1))) // [3, 1]
# TreeSet
TreeSet(): MutableSet<T> / TreeSet(other: Collection<T>): MutableSet<T>
The ascending-order set constructor, as sortedSetOf builds.
println(TreeSet(listOf(3, 1, 2))) // [1, 2, 3]
# ArrayList
ArrayList(): MutableList<T> / ArrayList(other: Collection<T>): MutableList<T>
The java.util.ArrayList constructor — empty, or a copy of another collection. A list keeps position order however it was built, so it has no ordering surprise the way its Set/Map siblings do.
println(ArrayList(listOf(3, 1))) // [3, 1]
# emptyMap
emptyMap(): Map<K, V>
Builds an empty Map, which prints as {}.
println(emptyMap<String, Int>()) // {}
# arrayOf
arrayOf(vararg elements: T): Array<T>
Builds a JVM-style array. The element values decide the descriptor at run time, so a boxed array prints as [Ljava.lang.Integer;@n and compares by identity.
val a = arrayOf(1, 2, 3)
println(a[1]) // 2
# intArrayOf
intArrayOf(vararg elements: Int): IntArray
Builds a primitive Int array from the given elements.
println(intArrayOf(1, 2).sum()) // 3
# doubleArrayOf
doubleArrayOf(vararg elements: Double): DoubleArray
Builds a primitive Double array.
println(doubleArrayOf(1.5, 2.5).sum()) // 4.0
# booleanArrayOf
booleanArrayOf(vararg elements: Boolean): BooleanArray
Builds a primitive Boolean array.
println(booleanArrayOf(true, false).size) // 2
# charArrayOf
charArrayOf(vararg elements: Char): CharArray
Builds a primitive Char array.
println(charArrayOf('a', 'b').joinToString("")) // ab
# IntArray
IntArray(size: Int): IntArray
IntArray(size: Int) { i -> … }: IntArray
Builds a zero-filled Int array of the given size, or fills each slot with the lambda applied to its index. A negative size raises NegativeArraySizeException.
println(IntArray(3).sum()) // 0
println(IntArray(3) { it * 2 }.sum()) // 6
# DoubleArray
DoubleArray(size: Int): DoubleArray
DoubleArray(size: Int) { i -> … }: DoubleArray
Builds a zero-filled Double array, or fills it from the index lambda.
println(DoubleArray(2).sum()) // 0.0
# BooleanArray
BooleanArray(size: Int): BooleanArray
BooleanArray(size: Int) { i -> … }: BooleanArray
Builds a zero-filled Boolean array, or fills it from the index lambda.
println(BooleanArray(3).size) // 3
# CharArray
CharArray(size: Int): CharArray
CharArray(size: Int) { i -> … }: CharArray
Builds a zero-filled Char array, or fills it from the index lambda.
println(CharArray(2).size) // 2
# Array
Array(size: Int) { i -> … }: Array<T>
Builds a generic array from an index lambda. Unlike the four primitive builders it exists *only* in the initializer form — Kotlin has no zero-filled Array(n) — and its descriptor is inferred from the elements the lambda produced.
println(Array(3) { it * 2 }.joinToString()) // 0, 2, 4
# StringBuilder
StringBuilder()
StringBuilder(text: CharSequence)
StringBuilder(capacity: Int)
A mutable character sequence. Every mutator (append, appendLine, insert, delete, replace, deleteCharAt, reverse, clear) answers the RECEIVER, so calls chain and keep building one object. setLength/setCharAt answer Unit; setLength pads with \u0000 when it grows. Content is held as UTF-16 code units, so length, sb[i], and every index argument count chars the way the JVM does. capacity() is modelled too: 16 by default, text.length + 16 from a text, and an append that does not fit grows to max(2 * cap + 2, needed). The read-only CharSequence members (length, indexOf, substring, startsWith, first, toList, …) behave exactly as on String. It overrides neither equals nor hashCode, so two builders holding the same text are NOT equal.
val sb = StringBuilder()
sb.append("a").append(1).append(true)
println(sb) // a1true
println(sb.length) // 6
# buildString
buildString(block: StringBuilder.() -> Unit): String
buildString(capacity: Int, block: StringBuilder.() -> Unit): String
Runs the block against a fresh StringBuilder bound as this — so append(x) needs no qualifier — and yields its toString(). The capacity overload is accepted and its hint discarded.
println(buildString { append("a"); append(1) }) // a1
# buildList
buildList(block: MutableList<T>.() -> Unit): List<T>
The list counterpart of buildString: a fresh mutable list bound as the block's this, yielded once the block has filled it. add/addAll inside need no qualifier.
println(buildList { add(1); addAll(listOf(2, 3)) }) // [1, 2, 3]
# listOfNotNull
listOfNotNull(vararg elements: T?): List<T>
Builds a List from its arguments with the nulls dropped. filterNotNull() is the member form, on any existing collection.
println(listOfNotNull(1, null, 3)) // [1, 3]
# repeat
repeat(times: Int, action: (Int) -> Unit)
Runs the block times times with the zero-based index as it, and yields Unit. A non-positive count runs it not at all.
repeat(3) { print(it) } // 012
# require
require(value: Boolean)
require(value: Boolean, lazyMessage: () -> Any)
requireNotNull(value: T?): T
Argument preconditions. A false condition throws IllegalArgumentException: Failed requirement., or the message the block produced — the block runs ONLY on the failing path. requireNotNull throws Required value was null. on null and otherwise answers the value.
try { require(1 > 2) { "bad input" } } catch (e: Exception) { println(e.message) } // bad input
# check
check(value: Boolean)
check(value: Boolean, lazyMessage: () -> Any)
checkNotNull(value: T?): T
error(message: Any): Nothing
State preconditions — the IllegalStateException mirror of require. The defaults are Check failed. and Required value was null.; error(msg) throws unconditionally with the message given.
try { check(false) } catch (e: Exception) { println(e.message) } // Check failed.
# TODO
TODO(): Nothing
TODO(reason: String): Nothing
Throws kotlin.NotImplementedError: An operation is not implemented, with : reason appended when one is given. It is an Error, not an Exception, so catch (e: Exception) does not catch it.
try { TODO("later") } catch (e: Throwable) { println(e) } // kotlin.NotImplementedError: An operation is not implemented: later
Math
# abs
abs(n: Int): Int
abs(x: Double): Double
Absolute value, keeping an Int result for an integral argument. Lives in kotlin.math, so it is unresolvable without import kotlin.math.abs or a star import — exactly as in Kotlin.
import kotlin.math.abs
fun main() { println(abs(-3)) } // 3
# sqrt
sqrt(x: Double): Double
Square root, always Double. Needs the kotlin.math import.
import kotlin.math.sqrt
fun main() { println(sqrt(9.0)) } // 3.0
# floor
floor(x: Double): Double
Largest Double no greater than the argument. Needs the kotlin.math import.
import kotlin.math.floor
fun main() { println(floor(-1.5)) } // -2.0
# ceil
ceil(x: Double): Double
Smallest Double no less than the argument. Needs the kotlin.math import.
import kotlin.math.ceil
fun main() { println(ceil(1.2)) } // 2.0
# round
round(x: Double): Double
Rounds to the closest integer as a Double, with **ties to even** — round(2.5) is 2.0 and round(3.5) is 4.0. This is kotlin.math.round; Math.round is the different, half-up, Long-returning one.
import kotlin.math.round
fun main() { println(round(2.5)) } // 2.0
# max
max(a: Int, b: Int): Int
max(a: Double, b: Double): Double
Larger of two values, keeping an Int result when both are integral. Lives in kotlin.math and needs the import; maxOf is the auto-imported spelling of the same operation.
import kotlin.math.max
fun main() { println(max(2, 9)) } // 9
# min
min(a: Int, b: Int): Int
min(a: Double, b: Double): Double
Smaller of two values, keeping an Int result when both are integral. Needs the kotlin.math import; minOf is the auto-imported spelling.
import kotlin.math.min
fun main() { println(min(2, 9)) } // 2
# maxOf
maxOf(a: T, b: T): T
Larger of two values. It lives in the auto-imported kotlin package, so unlike max it needs no import; it dispatches to the same implementation.
println(maxOf(2, 9)) // 9
# minOf
minOf(a: T, b: T): T
Smaller of two values, auto-imported like maxOf and sharing min's implementation.
println(minOf(2, 9)) // 2
# PI
PI: Double
The kotlin.math circle constant, in scope only under the import. It folds to a literal at compile time rather than paying a host dispatch. Also reachable as Math.PI, which needs no import.
import kotlin.math.PI
fun main() { println(PI) } // 3.141592653589793
E: Double
The kotlin.math base of the natural logarithm, in scope only under the import, and also reachable as Math.E.
import kotlin.math.E
fun main() { println(E) } // 2.718281828459045
# Math
Math.abs(…) Math.max(…) Math.min(…)
Math.sqrt(…) Math.floor(…) Math.ceil(…)
Math.round(…) Math.PI Math.E
The java.lang.Math statics. Kotlin auto-imports java.lang.* on the JVM, so these need no import line — which is the practical difference from the kotlin.math top-level spellings. A local binding or a user class named Math shadows the whole thing.
println(Math.abs(-3)) // 3
# Math.round
Math.round(x: Double): Long
The odd one out of the rounding family: **half-up** (floor(x + 0.5)) and returning a Long, where kotlin.math.round is ties-to-even and returns a Double. It dispatches under its own name for exactly that reason.
println(Math.round(2.5)) // 3
Throwables
# Throwable
Throwable()
Throwable(message: String)
The root of the modelled hierarchy — java.lang.Throwable. catch (e: Throwable) matches anything in flight, including a value that is not one of the sixteen built-in classes. A constructor with no argument leaves message null.
try { throw Throwable("boom") } catch (e: Throwable) { println(e.message) } // boom
# Exception
Exception()
Exception(message: String)
java.lang.Exception, the parent of RuntimeException. Catching it catches every runtime exception below it but not an Error.
try { 1 / 0 } catch (e: Exception) { println("caught") } // caught
# Error
Error()
Error(message: String)
java.lang.Error, a direct child of Throwable and a sibling of Exception — so catch (e: Exception) does *not* catch it.
try { throw Error("fatal") } catch (e: Throwable) { println("caught") } // caught
# RuntimeException
RuntimeException()
RuntimeException(message: String)
java.lang.RuntimeException, the parent of every fault the runtime itself raises.
try { throw RuntimeException("x") } catch (e: RuntimeException) { println(e.message) } // x
# ArithmeticException
ArithmeticException(message: String)
Raised by integer / and % when the divisor is zero, with the message / by zero. Floating-point division never raises it — it yields Infinity or NaN.
try { 1 / 0 } catch (e: ArithmeticException) { println(e.message) } // / by zero
# IllegalArgumentException
IllegalArgumentException(message: String)
Raised by a negative take/drop/repeat count, a non-positive range step, and digitToInt on a non-digit Char.
try { listOf(1).take(-1) } catch (e: IllegalArgumentException) { println("caught") } // caught
# IllegalStateException
IllegalStateException(message: String)
Never raised by the runtime itself — it is here for user code, most often as the right operand of ?:.
val x: Int? = null
try { x ?: throw IllegalStateException("missing") } catch (e: Exception) { println(e.message) }
# NumberFormatException
NumberFormatException(message: String)
A child of IllegalArgumentException. kotlinrs never raises it — there is no String.toInt to fail — so it exists for user code and for the hierarchy's shape.
try { throw NumberFormatException("bad") } catch (e: IllegalArgumentException) { println("caught") }
# IndexOutOfBoundsException
IndexOutOfBoundsException(message: String)
The parent of the string and array out-of-range classes. It is raised directly by removeAt past the end of a list and by an indexed write past the end.
try { mutableListOf(1).removeAt(5) } catch (e: IndexOutOfBoundsException) { println("caught") }
# StringIndexOutOfBoundsException
StringIndexOutOfBoundsException(message: String)
Raised by s[i] and substring when a UTF-16 offset falls outside the string.
try { "abc".substring(9) } catch (e: Exception) { println(e.message) }
# ArrayIndexOutOfBoundsException
ArrayIndexOutOfBoundsException(message: String)
Raised by an out-of-range indexed read on a List, Set or Array — note that the *method* form, get(i), raises NoSuchElementException instead, which diverges from Kotlin.
try { listOf(1, 2)[5] } catch (e: Exception) { println(e.message) }
# NullPointerException
NullPointerException()
NullPointerException(message: String)
Raised by !! on a null value. Since kotlinrs does no static null checking, !! is the only place the runtime produces one on its own.
val x: Int? = null
try { x!! } catch (e: NullPointerException) { println("caught") }
# ClassCastException
ClassCastException(message: String)
Modelled for the hierarchy but never raised: kotlinrs has no as cast operator, so there is no cast to fail.
try { throw ClassCastException("x") } catch (e: RuntimeException) { println("caught") }
# UnsupportedOperationException
UnsupportedOperationException(message: String)
Raised by reduce on an empty sequence, with the message Empty collection can't be reduced.
try { listOf<Int>().reduce { a, b -> a + b } } catch (e: Exception) { println("caught") }
# NegativeArraySizeException
NegativeArraySizeException(message: String)
Raised by an array builder given a negative size, such as IntArray(-1).
try { IntArray(-1) } catch (e: Exception) { println("caught") }
# NoSuchElementException
NoSuchElementException(message: String)
The one built-in throwable outside java.lang — it is java.util.NoSuchElementException, and the package is observable through toString. Raised by first, last, get, max and min on an empty sequence, with the message List is empty.
try { listOf<Int>().first() } catch (e: NoSuchElementException) { println(e.message) }
String Members
# length
String.length: Int
Number of UTF-16 code units — the JVM kotlin.String.length contract, not the Unicode scalar count. Every index, slice and indexOf result uses the same basis. A non-ASCII character written inside a string literal was split into UTF-8 bytes by the lexer, so its length is counted in those bytes.
println("abc".length) // 3
# uppercase
String.uppercase(): String
Full Unicode uppercase mapping, so a character whose mapping expands does expand (ß becomes SS). No locale argument is accepted.
println("abc".uppercase()) // ABC
# toUpperCase
String.toUpperCase(): String
The deprecated Kotlin spelling of uppercase, accepted here and dispatching to the same implementation.
println("abc".toUpperCase()) // ABC
# lowercase
String.lowercase(): String
Full Unicode lowercase mapping. No locale argument is accepted.
println("ABC".lowercase()) // abc
# toLowerCase
String.toLowerCase(): String
The deprecated Kotlin spelling of lowercase, dispatching to the same implementation.
println("ABC".toLowerCase()) // abc
# trim
String.trim(): String
Strips leading and trailing whitespace, by Unicode's whitespace definition. There is no predicate or char-set overload, and no trimStart/trimEnd/trimIndent.
println(" hi ".trim()) // hi
# isEmpty
String.isEmpty(): Boolean
True when the string has no code units at all.
println("".isEmpty()) // true
# isNotEmpty
String.isNotEmpty(): Boolean
The negation of isEmpty.
println("a".isNotEmpty()) // true
# isBlank
String.isBlank(): Boolean
True when the string is empty or made only of whitespace.
println(" ".isBlank()) // true
# isNotBlank
String.isNotBlank(): Boolean
The negation of isBlank.
println(" x ".isNotBlank()) // true
# contains
String.contains(other: Any): Boolean
Substring test. The argument is rendered by the same stringifier println uses, so a Char argument reads as its character — but an Int reads as its digits, which means "abc".contains(97) is false where the JVM's Char overload would say true. There is no ignoreCase parameter and no Regex overload.
println("abc".contains("bc")) // true
println("abc".contains('b')) // true
# startsWith
String.startsWith(prefix: Any): Boolean
Prefix test, with the argument rendered the same way contains renders it. No ignoreCase or start-offset parameter.
println("abc".startsWith("ab")) // true
# endsWith
String.endsWith(suffix: Any): Boolean
Suffix test. No ignoreCase parameter.
println("abc".endsWith("bc")) // true
# plus
String.plus(other: Any): String
The method spelling of + — what a + b compiles to on the JVM, and the form + has to take to be reached through a safe call.
println("ab".plus("c")) // abc
# replace
String.replace(old: Any, new: Any): String
Replaces every occurrence of a literal substring. There is no Regex overload and no ignoreCase parameter.
println("a-b-c".replace("-", "+")) // a+b+c
# repeat
String.repeat(n: Int): String
Concatenates the string with itself n times. n of zero yields the empty string; a negative n raises IllegalArgumentException: Count 'n' must be non-negative, but was N.
println("ab".repeat(2)) // abab
# indexOf
String.indexOf(needle: Any): Int
UTF-16 offset of the first occurrence, or -1 when there is none. There is no start-index parameter and no lastIndexOf.
println("abc".indexOf("c")) // 2
# substring
String.substring(start: Int): String
String.substring(start: Int, end: Int): String
Slice between two UTF-16 offsets, end exclusive and defaulting to the length. An out-of-range or inverted pair raises StringIndexOutOfBoundsException: Range [start, end) out of bounds for length N.
println("hello".substring(1, 3)) // el
Char Members
# code
Char.code: Int
The character's UTF-16 code unit as an Int. The inverse is Int.toChar().
println('A'.code) // 65
# digitToInt
Char.digitToInt(): Int
The decimal value of a digit character. Radix 10 only — no radix parameter — and a non-digit raises IllegalArgumentException: Char c is not a decimal digit. There is no digitToIntOrNull.
println('7'.digitToInt()) // 7
# isDigit
Char.isDigit(): Boolean
True for a Unicode numeric character. The classification delegates to Rust's Unicode tables, which agree with the JVM's Character over ASCII.
println('7'.isDigit()) // true
# isLetter
Char.isLetter(): Boolean
True for a Unicode alphabetic character. A lone surrogate half is neither letter nor digit, on this runtime and on the JVM alike.
println('a'.isLetter()) // true
# isLetterOrDigit
Char.isLetterOrDigit(): Boolean
True for a Unicode alphanumeric character.
println('_'.isLetterOrDigit()) // false
# isWhitespace
Char.isWhitespace(): Boolean
True for a Unicode whitespace character.
println(' '.isWhitespace()) // true
# isUpperCase
Char.isUpperCase(): Boolean
True for a Unicode uppercase character.
println('A'.isUpperCase()) // true
# isLowerCase
Char.isLowerCase(): Boolean
True for a Unicode lowercase character.
println('a'.isLowerCase()) // true
# uppercaseChar
Char.uppercaseChar(): Char
The uppercase mapping as a single Char. When the mapping would expand to more than one character the original is kept — the JVM's Character.toUpperCase(char) contract, so 'ß'.uppercaseChar() is still 'ß'.
println('a'.uppercaseChar()) // A
# lowercaseChar
Char.lowercaseChar(): Char
The lowercase mapping as a single Char, keeping the original when the mapping would expand.
println('A'.lowercaseChar()) // a
# uppercase
Char.uppercase(): String
The full uppercase mapping as a String, which is what lets an expanding mapping expand — unlike uppercaseChar.
println('a'.uppercase()) // A
# lowercase
Char.lowercase(): String
The full lowercase mapping as a String.
println('A'.lowercase()) // a
# compareTo
Char.compareTo(other: Char): Int
Orders two characters by code unit. It returns the sign only — -1, 0 or 1 — where the JVM returns the code-unit difference. The sign is all Kotlin's Comparable contract promises, but a program that reads the magnitude will see a different number here.
println('b'.compareTo('a')) // 1
# plus
Char.plus(n: Int): Char
Displaces the code unit upward, yielding a Char. The result wraps into 16 bits.
println('A'.plus(1)) // B
# minus
Char.minus(n: Int): Char
Char.minus(other: Char): Int
Two behaviours chosen by the argument: subtracting an Int displaces the code unit and yields a Char; subtracting another Char yields the Int distance between them.
println('c'.minus(1)) // b
println('c'.minus('a')) // 2
# equals
Char.equals(other: Any?): Boolean
Code-unit equality. A Char is a tagged handle rather than a heap object, so this needs no heap read.
println('a'.equals('a')) // true
# hashCode
Char.hashCode(): Int
The code unit itself, matching the JVM's Character.hashCode.
println('A'.hashCode()) // 65
# toString
Char.toString(): String
The one-character string. The compiler resolves this statically from the receiver's type rather than through the generic stringifier, because the runtime representation alone could not tell a Char from a number.
println('A'.toString() + "!") // A!
Sequence Members
# size
size: Int
Element count. Implemented once and shared by List, Set, Array and a range, so the four cannot drift apart. count() with no argument is the same call.
println(listOf(1, 2, 3).size) // 3
# count
count(): Int
Element count, identical to size when called with no argument. With a lambda it is instead the predicate-counting higher-order function.
println((1..5).count()) // 5
# isEmpty
isEmpty(): Boolean
True when the sequence holds no elements.
println(emptyList<Int>().isEmpty()) // true
# isNotEmpty
isNotEmpty(): Boolean
The negation of isEmpty.
println(listOf(1).isNotEmpty()) // true
# first
first(): T
The first element. On a range it is instead a progression *property* — the start value, defined even when the range is empty. On a list, set or array an empty receiver raises NoSuchElementException: List is empty. There is no firstOrNull and no predicate overload.
println(listOf(10, 20).first()) // 10
# last
last(): T
The last element, or a range's end value as a progression property. An empty list, set or array raises NoSuchElementException. There is no lastOrNull.
println(listOf(10, 20).last()) // 20
# get
get(index: Int): T
Element at a position. Out of range it raises NoSuchElementException: List is empty. — which diverges from Kotlin, and from the [] operator on the same receiver, which raises ArrayIndexOutOfBoundsException.
println(listOf(10, 20).get(1)) // 20
# contains
contains(element: T): Boolean
Membership by the receiver's own equality rule. A List compares with equals; a Set reaches equals only after the hashes agree. So a data class element or a class with a declared equals is found by value, while a class that declares neither compares by reference identity and is not found by a fresh construction.
println(listOf(1, 2).contains(2)) // true
# containsAll
containsAll(elements: Collection<T>): Boolean
Whether every element of the argument is present, by the same rule contains uses. Vacuously true for an empty argument.
println(listOf(1, 2, 3).containsAll(listOf(1, 3))) // true
# indexOf
indexOf(element: T): Int
Position of the first structurally equal element, or -1. There is no lastIndexOf and no indexOfFirst.
println(listOf("a", "b").indexOf("b")) // 1
# sum
sum(): Int
sum(): Double
Adds the elements. The result is an Int when every element is integral and a Double otherwise — so a mixed sequence sums to a Double. An empty sequence sums to 0.
println(listOf(1, 2, 3).sum()) // 6
# average
average(): Double
Arithmetic mean, always a Double. An empty sequence averages to NaN rather than raising, matching Kotlin.
println(listOf(1, 2).average()) // 1.5
# max
max(): T
The largest element — strings lexicographically, chars by code unit, everything else numerically. An empty sequence raises NoSuchElementException. kotlinrs implements the pre-1.4 spelling only: there is no maxOrNull.
println(listOf(3, 1, 2).max()) // 3
# min
min(): T
The smallest element, by the same ordering as max, raising on an empty sequence. There is no minOrNull.
println(listOf(3, 1, 2).min()) // 1
# toList
toList(): List<T>
A List holding the same elements, in order. On a range this is what materializes it.
println((1..3).toList()) // [1, 2, 3]
# toMutableList
toMutableList(): MutableList<T>
The same copy toList makes — List and MutableList are one runtime object here.
println(setOf(1, 2).toMutableList()) // [1, 2]
# toTypedArray
toTypedArray(): Array<T>
Yields a List, not an Array — it shares toList's implementation. This diverges from Kotlin: the result prints its elements as [1, 2] and compares structurally, where a real array would print its JVM descriptor and compare by identity.
println(listOf(1, 2).toTypedArray()) // [1, 2]
# asList
asList(): List<T>
A List over the same elements. It is a copy here rather than a view, so mutating the receiver afterwards does not show through.
println(intArrayOf(1, 2).asList()) // [1, 2]
# toSet
toSet(): Set<T>
A Set of the distinct elements, first occurrence kept, in encounter order.
println(listOf(3, 1, 3).toSet()) // [3, 1]
# toMutableSet
toMutableSet(): MutableSet<T>
The same distinct Set toSet builds.
println(listOf(1, 1).toMutableSet()) // [1]
# toHashSet
toHashSet(): MutableSet<T>
The same insertion-ordered distinct Set. Kotlin's HashSet gives no order guarantee; here encounter order is always kept.
println(listOf(2, 1, 2).toHashSet()) // [2, 1]
# distinct
distinct(): List<T>
The distinct elements as a **List**, where toSet gives the same elements as a Set. That is the whole difference between the two, and it decides how the result compares and prints.
println(listOf(3, 1, 3).distinct()) // [3, 1]
# plus
plus(element: T): List<T> / plus(elements: Iterable<T>): List<T>
The receiver's elements followed by the argument's, as a collection of the receiver's own kind. Spelled + as an operator. The Iterable overload wins whenever the argument is one, so listOf(listOf(1)) + listOf(2) is [[1], 2]; use plusElement to append a collection whole. A Map receiver takes a Pair, another Map, or an iterable of pairs, and an existing key keeps its position while its value is replaced.
println(listOf(1, 2) + 3) // [1, 2, 3]
# minus
minus(element: T): List<T> / minus(elements: Iterable<T>): List<T>
The receiver without the argument's elements, as a collection of the receiver's own kind. Spelled - as an operator. The element form drops only the FIRST match — listOf(1, 2, 2, 3) - 2 is [1, 2, 3] — while the Iterable form drops every occurrence of every listed element. A Map receiver takes a key or an iterable of keys.
println(listOf(1, 2, 3) - 2) // [1, 3]
# plusElement
plusElement(element: T): List<T>
The receiver's elements followed by the argument as ONE element, pinning the overload plus would otherwise resolve to the Iterable form for a collection argument.
println(listOf(1, 2).plusElement(listOf(3))) // [1, 2, [3]]
# minusElement
minusElement(element: T): List<T>
The receiver without the first element equal to the argument, treating it as one element rather than a sequence to subtract.
println(listOf(1, 2, 2).minusElement(2)) // [1, 2]
# union
union(other: Iterable<T>): Set<T>
Distinct elements of the receiver followed by those of the argument, as a Set. Defined on any iterable receiver, and always returning a Set whatever kind the receiver was. Only the method form parses — a union b as an infix call is a syntax error.
println(listOf(1, 2).union(listOf(2, 3))) // [1, 2, 3]
# intersect
intersect(other: Iterable<T>): Set<T>
Distinct receiver elements that also appear in the argument, as a Set.
println(listOf(1, 2, 3).intersect(listOf(2, 3, 4))) // [2, 3]
# subtract
subtract(other: Iterable<T>): Set<T>
Distinct receiver elements that do not appear in the argument, as a Set.
println(listOf(1, 2, 3).subtract(listOf(2))) // [1, 3]
# sorted
sorted(): List<T>
Ascending sort into a new List — strings lexicographically, chars by code unit, everything else numerically. sortedWith takes an explicit ordering; there is no in-place sort.
println(listOf(3, 1, 2).sorted()) // [1, 2, 3]
# sortedDescending
sortedDescending(): List<T>
Descending sort into a new List.
println(listOf(3, 1, 2).sortedDescending()) // [3, 2, 1]
# take
take(n: Int): List<T>
The first n elements as a List. An oversized n clamps to the whole sequence; a negative one raises IllegalArgumentException: Requested element count N is less than zero. See takeLast for the other end and takeWhile for the predicate form.
println(listOf(1, 2, 3).take(2)) // [1, 2]
# takeLast
takeLast(n: Int): List<T>
The LAST n elements, with the same clamping and the same negative-count error as take. On a String receiver it answers a String, not a List<Char>.
println(listOf(1, 2, 3).takeLast(2)) // [2, 3]
println("abcde".takeLast(2)) // de
# drop
drop(n: Int): List<T>
Everything after the first n elements, as a List. An oversized n yields an empty list; a negative one raises IllegalArgumentException. See dropLast for the other end and dropWhile for the predicate form.
println(listOf(1, 2, 3).drop(1)) // [2, 3]
# dropLast
dropLast(n: Int): List<T>
Everything but the last n elements — the complement of takeLast at the same cut. On a String receiver it answers a String.
println(listOf(1, 2, 3).dropLast(1)) // [1, 2]
println("abcde".dropLast(2)) // abc
# generateSequence
generateSequence(seed: T?, next: (T) -> T?): Sequence<T>
A LAZY sequence: next runs on demand and ends the sequence by answering null. It is the one sequence here that is not materialized up front, because it is the one that can be endless — generateSequence(1) { it * 2 } is bounded only by a take, a takeWhile, or a short-circuiting search. map/filter/filterNot/takeWhile/dropWhile/take/drop stay lazy on it; anything else pulls the pipeline into a List first, which on an unbounded one raises rather than hanging.
println(generateSequence(1) { it * 2 }.take(5).toList()) // [1, 2, 4, 8, 16]
println(generateSequence(1) { if (it < 20) it * 2 else null }.toList()) // [1, 2, 4, 8, 16, 32]
# splitToSequence
String.splitToSequence(vararg delimiters: String): Sequence<String>
split answering a sequence. The receiver bounds the result either way, so it is materialized like every other finite sequence here.
println("a,b,c".splitToSequence(",").toList()) // [a, b, c]
# unzip
unzip(): Pair<List<A>, List<B>>
zip run backwards: a sequence of pairs becomes a pair of sequences. A non-pair element is an error.
println(listOf(1 to "a", 2 to "b").unzip()) // ([1, 2], [a, b])
# zipWithNext
zipWithNext(): List<Pair<T, T>>
zipWithNext(transform: (T, T) -> R): List<R>
Pairs each element with its SUCCESSOR, so the result is one shorter than the receiver and empty for a receiver of one or none. The lambda overload transforms each such pair instead of yielding it.
println(listOf(1, 2, 3).zipWithNext()) // [(1, 2), (2, 3)]
println(listOf(1, 2, 3).zipWithNext { a, b -> a + b }) // [3, 5]
# joinToString
joinToString(): String
joinToString(separator: Any): String
Renders the elements with the same stringifier println uses, joined by a separator that defaults to ", ". The positional prefix, postfix, limit and truncated parameters are honoured, and a trailing lambda transforms each element.
println(listOf(1, 2, 3).joinToString("-")) // 1-2-3
# reversed
reversed(): List<T>
IntRange.reversed(): IntProgression
Reverses the order. On a list, set or array the result is a reversed List; on a range it is instead a descending IntProgression, which prints in its b downTo a step 1 form.
println(listOf(1, 2, 3).reversed()) // [3, 2, 1]
println((1..3).reversed()) // 3 downTo 1 step 1
Map Members
# size
Map.size: Int
Number of entries.
println(mapOf("a" to 1, "b" to 2).size) // 2
# isEmpty
Map.isEmpty(): Boolean
True when the map has no entries.
println(emptyMap<String, Int>().isEmpty()) // true
# isNotEmpty
Map.isNotEmpty(): Boolean
The negation of isEmpty.
println(mapOf("a" to 1).isNotEmpty()) // true
# containsKey
Map.containsKey(key: K): Boolean
Whether a structurally equal key is present. It is a linear scan of the entry list, not a hash lookup. There is no containsValue.
println(mapOf("a" to 1).containsKey("a")) // true
# get
Map.get(key: K): V?
The value under a key, or null when the key is absent — the same lookup m[k] performs. It never raises. getOrElse(key) { … } supplies a fallback; there is no getOrDefault.
println(mapOf("a" to 1).get("z")) // null
# keys
Map.keys: Set<K>
The keys in the map's iteration order, as a Set — so it compares order-insensitively against another Set, as Kotlin's does.
println(mapOf("a" to 1, "b" to 2).keys) // [a, b]
# values
Map.values: List<V>
The values in the map's iteration order, as a List. entries yields the key/value entries themselves, which print as k=v.
println(mapOf("a" to 1, "b" to 2).values) // [1, 2]
# toList
Map.toList(): List<Pair<K, V>>
The entries as Pairs, in iteration order. The pairs print (k, v) where the entries themselves print k=v, which is the whole reason this is not the generic sequence toList.
println(mapOf(1 to "a", 2 to "b").toList()) // [(1, a), (2, b)]
# filterKeys
Map.filterKeys(predicate: (K) -> Boolean): Map<K, V>
The entries whose KEY satisfies the predicate. The lambda sees the key alone — not the entry, as mapKeys does.
println(mapOf(1 to "a", 2 to "b").filterKeys { it > 1 }) // {2=b}
# filterValues
Map.filterValues(predicate: (V) -> Boolean): Map<K, V>
The entries whose VALUE satisfies the predicate, with the lambda seeing the value alone.
println(mapOf(1 to "a", 2 to "b").filterValues { it == "a" }) // {1=a}
# toSortedMap
Map.toSortedMap(): Map<K, V>
A java.util.TreeMap over the same entries: ascending natural key order, kept across later writes rather than applied once.
println(mapOf(2 to "b", 1 to "a").toSortedMap()) // {1=a, 2=b}
# put
MutableMap.put(key: K, value: V): V?
Sets a key, appending the entry when it is new. It answers the previous value, or null when there was none — the same write m[k] = v performs.
val m = mutableMapOf("a" to 1)
println(m.put("a", 2)) // 1
# remove
MutableMap.remove(key: K): Boolean
Drops the entry under a key. It answers a Boolean — whether anything was removed — where Kotlin answers the removed value or null.
val m = mutableMapOf("a" to 1)
println(m.remove("a")) // true
Higher-Order Collection Functions
# map
map(transform: (T) -> R): List<R>
Applies the lambda to every element and collects the results into a List. The receiver may be a List, Set, Array, range or String — a range materializes first, which is what makes (1..3).map { … } work, and a String iterates its characters. A Pair receiver is an unresolved reference.
println(listOf(1, 2, 3).map { it * 2 }) // [2, 4, 6]
# mapIndexed
mapIndexed(transform: (Int, T) -> R): List<R>
Like map, but the lambda takes the element's index first and the element second. forEachIndexed, filterIndexed and flatMapIndexed take the same index-first shape.
println(listOf(1, 2, 3).mapIndexed { i, v -> i * v }) // [0, 2, 6]
# flatMap
flatMap(transform: (T) -> Iterable<R>): List<R>
Applies the lambda to every element and splices each iterable result into one flat List. A result that is not iterable contributes nothing rather than raising.
println(listOf(1, 2).flatMap { listOf(it, it) }) // [1, 1, 2, 2]
# flatMapIndexed
flatMapIndexed(transform: (Int, T) -> Iterable<R>): List<R>
Like flatMap, but the lambda takes the element's index first and the element second.
println(listOf(1, 2).flatMapIndexed { i, v -> listOf(i, v) }) // [0, 1, 1, 2]
# mapNotNull
mapNotNull(transform: (T) -> R?): List<R>
Like map, but a result that is null is dropped instead of collected.
println(listOf(1, 2, 3).mapNotNull { if (it > 1) it else null }) // [2, 3]
# onEach
onEach(action: (T) -> Unit): C
Runs the lambda once per element for its side effect and answers the RECEIVER, so it chains where forEach (which answers Unit) cannot. On a String receiver the result is the string.
println(listOf(1, 2).onEach { print(it) }.size) // 122
# runningFold
runningFold(initial: R, operation: (R, T) -> R): List<R>
Like fold, but collects every intermediate accumulator INCLUDING the initial one, so the result is one longer than the receiver. scan is the same function under its other name.
println(listOf(1, 2, 3).runningFold(0) { a, n -> a + n }) // [0, 1, 3, 6]
# runningReduce
runningReduce(operation: (T, T) -> T): List<T>
Like reduce, but collects every intermediate accumulator. The first element seeds it, so an empty receiver answers an empty list rather than raising.
println(listOf(1, 2, 3).runningReduce { a, n -> a + n }) // [1, 3, 6]
# withIndex
withIndex(): List<IndexedValue<T>>
Pairs every element with its position. Each entry is an IndexedValue whose index and value are read as ordinary properties, which is what makes it print as IndexedValue(index=0, value=a) rather than as a Pair.
println("ab".withIndex().map { it.index.toString() + it.value }) // [0a, 1b]
# filter
filter(predicate: (T) -> Boolean): List<T>
Keeps the elements whose predicate returns exactly true; a null or non-Boolean result counts as false. The result is always a List, even from a Set receiver.
println(listOf(1, 2, 3, 4).filter { it % 2 == 0 }) // [2, 4]
# filterNot
filterNot(predicate: (T) -> Boolean): List<T>
Keeps the elements whose predicate does *not* hold. There is no filterNotNull or filterIsInstance.
println(listOf(1, 2, 3).filterNot { it > 2 }) // [1, 2]
# forEach
forEach(action: (T) -> Unit): Unit
Runs the lambda once per element for its side effect and yields Unit.
listOf(1, 2).forEach { print(it) } // 12
# fold
fold(initial: R, operation: (R, T) -> R): R
Threads an accumulator through the sequence left to right, starting from the given initial value. The lambda takes the accumulator first. It is the one higher-order function here that takes a non-lambda argument as well.
println(listOf(1, 2, 3).fold(0) { acc, n -> acc + n }) // 6
# foldRight
foldRight(initial: R, operation: (T, R) -> R): R
fold from the END, and — the part that catches people — its lambda takes the ELEMENT first and the accumulator second, the opposite of fold. Both differences show at once on the same lambda.
println(listOf(1, 2, 3).foldRight("") { a, b -> "$a$b" }) // 123
# reduce
reduce(operation: (T, T) -> T): T
Like fold but seeded with the first element instead of an explicit initial. An empty receiver raises UnsupportedOperationException: Empty collection can't be reduced.
println(listOf(1, 2, 3).reduce { a, b -> a * b }) // 6
# reduceRight
reduceRight(operation: (T, T) -> T): T
reduce from the end: seeded with the LAST element, walking backwards, with the lambda taking (element, acc). Same error on an empty receiver.
println(listOf(1, 2, 3).reduceRight { a, b -> a - b }) // 2
# any
any(predicate: (T) -> Boolean): Boolean
True as soon as one element satisfies the predicate, short-circuiting on the first hit. The no-argument any() overload is not implemented — use isNotEmpty().
println(listOf(1, 2, 3).any { it > 2 }) // true
# all
all(predicate: (T) -> Boolean): Boolean
True when every element satisfies the predicate, short-circuiting on the first failure. Vacuously true for an empty receiver.
println(listOf(2, 4).all { it % 2 == 0 }) // true
# none
none(predicate: (T) -> Boolean): Boolean
True when no element satisfies the predicate, short-circuiting on the first hit.
println(listOf(1, 2).none { it > 5 }) // true
# count
count(predicate: (T) -> Boolean): Int
How many elements satisfy the predicate. Called with no argument, count() is instead the sequence member that reports size.
println(listOf(1, 2, 3).count { it > 1 }) // 2
# sumOf
sumOf(selector: (T) -> Int): Int
sumOf(selector: (T) -> Double): Double
Sums the lambda's results, yielding an Int when every one is integral and a Double otherwise — the same rule sum() uses.
println(listOf("ab", "c").sumOf { it.length }) // 3
# maxByOrNull
maxByOrNull(selector: (T) -> R): T?
The element whose selector value is largest, or null when the receiver is empty. Ties keep the first such element. The selector runs once per element.
println(listOf("a", "abc").maxByOrNull { it.length }) // abc
# minByOrNull
minByOrNull(selector: (T) -> R): T?
The element whose selector value is smallest, or null on an empty receiver, keeping the first of any tie.
println(listOf("abc", "a").minByOrNull { it.length }) // a
# sortedBy
sortedBy(selector: (T) -> R): List<T>
Ascending sort by the selector's value, evaluated once per element and stable — equal keys keep their input order.
println(listOf("abc", "a").sortedBy { it.length }) // [a, abc]
# sortedByDescending
sortedByDescending(selector: (T) -> R): List<T>
Descending sort by the selector's value. The comparison is flipped rather than the result reversed, so ties still come out in input order as Kotlin requires.
println(listOf("a", "abc").sortedByDescending { it.length }) // [abc, a]
# sortedWith
sortedWith(comparator: Comparator<T>): List<T>
sortedWith(compare: (T, T) -> Int): List<T>
Stable sort under an explicit ordering — either a Comparator from compareBy or a plain two-argument lambda answering a negative/zero/positive Int.
println(listOf(3, 1, 2).sortedWith(compareBy { it })) // [1, 2, 3]
println(listOf(3, 1, 2).sortedWith { a, b -> b - a }) // [3, 2, 1]
# compareBy
compareBy(vararg selectors: (T) -> R): Comparator<T>
Builds a Comparator from one or more key selectors, compared left to right with the first non-equal key deciding. Extend it with thenBy/thenByDescending.
println(listOf(3, 1, 2).sortedWith(compareBy { it })) // [1, 2, 3]
# compareByDescending
compareByDescending(vararg selectors: (T) -> R): Comparator<T>
compareBy with every key reversed.
println(listOf(1, 3, 2).sortedWith(compareByDescending { it })) // [3, 2, 1]
# thenBy
Comparator<T>.thenBy(selector: (T) -> R): Comparator<T>
A NEW comparator with one more key appended as a tiebreak — the receiver is unchanged, as Kotlin's immutable comparators are. The added key is consulted only when every earlier key ties.
val byLen = compareBy<String> { it.length }
println(listOf("cc", "a", "bb").sortedWith(byLen.thenBy { it })) // [a, bb, cc]
# thenByDescending
Comparator<T>.thenByDescending(selector: (T) -> R): Comparator<T>
thenBy with the added key reversed. Each key carries its own direction, so an ascending key can be broken by a descending one.
val byLen = compareBy<String> { it.length }
println(listOf("cc", "a", "bb").sortedWith(byLen.thenByDescending { it })) // [a, cc, bb]
# associate
associate(transform: (T) -> Pair<K, V>): Map<K, V>
Builds a Map from the Pair each lambda call returns. A lambda result that is not a Pair raises kotlin: associate expects a Pair. Later duplicate keys overwrite earlier ones.
println(listOf(1, 2).associate { it to it * it }) // {1=1, 2=4}
# associateBy
associateBy(keySelector: (T) -> K): Map<K, T>
Builds a Map whose keys are the lambda's results and whose values are the elements — the mirror image of associateWith.
println(listOf("ab", "c").associateBy { it.length }) // {2=ab, 1=c}
# associateWith
associateWith(valueSelector: (T) -> V): Map<T, V>
Builds a Map keyed by the elements, with the lambda's results as the values.
println(listOf(1, 2, 3).associateWith { it * 2 }) // {1=2, 2=4, 3=6}
# groupBy
groupBy(keySelector: (T) -> K): Map<K, List<T>>
Buckets the elements by the lambda's result. Keys appear in first-encounter order and each bucket keeps its elements in input order.
println(listOf(1, 2, 3, 4).groupBy { it % 2 }) // {1=[1, 3], 0=[2, 4]}
# groupingBy
groupingBy(keySelector: (T) -> K): Grouping<T, K>
Pairs the source with a key selector and does no work yet — the result is consumed by a terminal operation such as eachCount. That laziness is the difference from groupBy, which materializes the per-key lists.
println(listOf(1, 2, 3, 4).groupingBy { it % 2 }.eachCount()) // {1=2, 0=2}
# eachCount
eachCount(): Map<K, Int>
How many elements fell under each key of a Grouping. The keys come out in first-encounter order, as the LinkedHashMap it fills.
println(listOf("a", "bb", "cc").groupingBy { it.length }.eachCount()) // {1=1, 2=2}