Numeric Types
Kotlin supports Java 8's underscore notation for numeric literals to improve readability:
val million = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
Type Suffixes and Explicit Type Declaration
To specify numeric types explicitly, use type annotations or suffixes:
val floatValue: Float = 1 // Using type annotation
val doubleValue = 1f // Using suffix
val longValue = 123L // Long suffix
No Implicit Widening
Kotlin does not perform implicit type widening. Attempting to pass an Int where a Long or Float is expected results in a compilation error:
val i = 1
val d = 1.1
val f = 1.1f
fun printDouble(d: Double) { println(d) }
printDouble(d) // OK
printDouble(i) // Error: type mismatch
printDouble(f) // Error: type mismatch
Supported Numeric Formats
Kotlin supports three numeric literal formats:
val decimal = 123 // Decimal
val hex = 0x0F // Hexadecimal
val binary = 0b0101 // Binary
val longDecimal = 123L // Long type suffix
Note: Octal notation is not supported in Kotlin.
Boxing and Identity
When using nullable references (Int?) or generics, numeric values are boxed. Boxed values maintain equality but may not maintain identity:
val primitive: Int = 10000
println(primitive == primitive) // true - same object
val boxed: Int? = primitive
val anotherBoxed: Int? = primitive
println(boxed === anotherBoxed) // false - different objects
println(boxed == anotherBoxed) // true - values equal
Explicit Type Conversion
Kotlin requires explicit conversion between numeric types. The compiler no longer implicitly converts Int to Long (this was changed in later versions):
val byteValue: Byte = 1
// val intValue: Int = byteValue // Error: no implicit conversion
val intValue: Int = byteValue.toInt() // Explicit conversion
// Available conversion functions:
val num: Int = 42
val l: Long = num.toLong()
val d: Double = num.toDouble()
val f: Float = num.toFloat()
val s: Short = num.toShort()
val b: Byte = num.toByte()
val c: Char = num.toChar()
When performing arithmetic with mixed types, the result follows the widest type involved:
val result = 1L + 3 // Result is Long
Arithmetic Operations
Integer division always yields integers. For floating-point results, explicitly convert at least one operand:
val x = 5 / 2.toDouble()
println(x == 2.5) // true
Bitwise Operations
Kotlin uses named functions for bitwise operations, called using infix notation:
val shifted = (1 shl 2) and 0x000FF000
// Available bitwise functions (Int and Long only):
// shl - signed left shift
// shr - signed right shift
// ushr - unsigned right shift
// and - bitwise AND
// or - bitwise OR
// xor - bitwise XOR
// inv - bitwise inversion
Ranges
Ranges are created using the .. operator:
val validRange = 1..100
val isInRange = 50 in validRange // true
val isNotInRange = 0 !in validRange // true
Characters
Character literals use single quotes. They cannot be implicitly converted to numbers:
val c: Char = 'a'
val digit: Int = c.toInt() // Explicit conversion required
// Escape sequences: \t, \b, \n, \r, \', \", \\, \$
// Unicode escape: '\uFF00'
fun decimalDigitValue(c: Char): Int {
require(c in '0'..'9') { "Not a digit" }
return c.toInt() - '0'.toInt()
}
Arrays
Kotlin arrays are represented by the Array class and are invariant (cannot assign Array<String> to Array<Any>):
val squares = Array(5) { i -> (i * i).toString() }
// Creates: ["0", "1", "4", "9", "16"]
Primitive Type Arrays
For better performance, use primitive array types without boxing:
val primes = intArrayOf(2, 3, 5, 7, 11)
primes[0] = primes[1] + primes[2]
// Array initialized to zeros
val zeroArray = IntArray(5)
// Array initialized to constant value
val fixedArray = IntArray(5) { 42 }
// Array initialized with index-based values
val indexedArray = IntArray(5) { it * it }
Unsigned Integers (Kotlin 1.3+)
Unsigned integer types are available as experimental features:
val unsigned: UInt = 100u
val byteValue: UByte = 255u
// Specialized arrays for unsigned types:
val uIntArray = UIntArray(10)
val uLongArray = ULongArray(10)
To use unsigned types, opt-in to the experimental API:
@OptIn(ExperimentalUnsignedTypes::class)
fun useUnsigned() {
val value: UInt = 42u
}
Strings
Raw Strings
Triple-quoted strings preserve formatting without escaping:
val poem = """
|The road not taken
|makes all the difference
|Robert Frost
""".trimMargin()
// Output:
// The road not taken
// makes all the difference
// Robert Frost
The trimMargin() function removes leading whitespace, using | as the default margin prefix.
String Templates
Embed variables and expressions directly in strings:
val name = "World"
println("Hello, $name!")
val str = "Kotlin"
println("${str.length} characters in ${str}")
Package Imports
Import statements follow Java conventions. Use the as keyword to resolve naming conlficts:
import org.example.Message
import org.test.Message as TestMessage
Control Flow
If Expression
In Kotlin, if is an expression that returns a value. The else branch is required when used as an expression:
// Traditional usage
val max = if (a > b) a else b
// Multi-line expression with blocks
val result = if (condition) {
performAction()
"success"
} else {
handleFallback()
"failure"
}
When Expression
The when construct replaces switch statements. It must have an else branch when used as an expression:
when (value) {
1 -> println("one")
2 -> println("two")
else -> println("other")
}
// Multiple conditions
when (value) {
0, 1 -> println("zero or one")
else -> println("other")
}
// Using arbitrary expressions
when (value) {
parseInt(input) -> println("matched input")
else -> println("no match")
}
// Range checks
when (value) {
in 1..10 -> println("in range")
!in 20..30 -> println("outside range")
else -> println("middle ground")
}
// Type checks with smart cast
fun describe(obj: Any): String = when (obj) {
is String -> "String of length ${obj.length}"
is Int -> "Integer value $obj"
else -> "Unknown type"
}
// Replacement for if-else chains
when {
x.isOdd() -> println("odd")
y.isEven() -> println("even")
else -> println("impossible")
}
// Capturing subject (Kotlin 1.3+)
fun getResponse(): String = when (val response = fetchResponse()) {
is Success -> response.data
is Error -> throw Exception(response.message)
}
For Loop
The for loop iterates over anything with a iterator() functon:
// Basic iteration
for (item in collection) {
println(item)
}
// With explicit type
for (item: Int in numbers) {
// process item
}
// Range iteration with step and reverse
for (i in 1..5) println(i) // 1, 2, 3, 4, 5
for (i in 5 downTo 1) println(i) // 5, 4, 3, 2, 1
for (i in 0..10 step 2) println(i) // 0, 2, 4, 6, 8, 10
// Index-based iteration
val array = arrayOf("a", "b", "c")
for (i in array.indices) {
println("$i: ${array[i]}")
}
// With index and value
for ((index, value) in array.withIndex()) {
println("Index $index holds $value")
}
While Loop
Both while and do-while loops are available:
while (condition) {
// execute loop body
}
do {
val result = process()
} while (result != null) // result accessible in condition
Jump Expressions with Labels
Break with Label
Labels allow breaking to a specific outer loop:
outerLoop@ for (i in 1..10) {
for (j in 1..10) {
if (condition(i, j)) {
break@outerLoop // Exits the outer loop
}
}
}
Return with Label
Labels are particularly useful for returning from lambdas:
// Using explicit label
fun process() {
listOf(1, 2, 3, 4, 5).forEach label@{
if (it == 3) return@label
println(it)
}
println("Completed")
}
// Using implicit label (same name as function)
fun process() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach
println(it)
}
}
// Using anonymous function (return goes to function caller)
fun process() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return
println(value)
})
}
When returning a value with a label, use the label to specify the return target:
return@map transform(item)