Kotlin Fundamentals: Primitive Types, Control Structures, and Jump Expressions

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)

Tags: kotlin Programming Languages Control Flow Data Types Jump Expressions

Posted on Thu, 04 Jun 2026 16:05:40 +0000 by BobcatM