Skip to content

Data Types

Overview

Kotlin has a rich type system, including basic types, collection types, function types, and more. This chapter provides detailed coverage of Kotlin's type system, including type inference, null safety, generics, and other important concepts.

Basic Data Types

Numeric Types

kotlin
fun main() {
    // Integer types
    val byte: Byte = 127                    // 8-bit, -128 to 127
    val short: Short = 32767                // 16-bit, -32768 to 32767
    val int: Int = 2147483647              // 32-bit, -2^31 to 2^31-1
    val long: Long = 9223372036854775807L  // 64-bit, -2^63 to 2^63-1
    
    // Floating-point types
    val float: Float = 3.14f               // 32-bit IEEE 754
    val double: Double = 3.14159265359     // 64-bit IEEE 754
    
    println("Integer types:")
    println("Byte: $byte (${Byte.MIN_VALUE} to ${Byte.MAX_VALUE})")
    println("Short: $short (${Short.MIN_VALUE} to ${Short.MAX_VALUE})")
    println("Int: $int (${Int.MIN_VALUE} to ${Int.MAX_VALUE})")
    println("Long: $long (${Long.MIN_VALUE} to ${Long.MAX_VALUE})")
    
    println("\nFloating-point types:")
    println("Float: $float")
    println("Double: $double")
}

Numeric Type Conversion

kotlin
fun main() {
    val int: Int = 42
    
    // Explicit conversion (Kotlin doesn't support implicit conversion)
    val long: Long = int.toLong()
    val double: Double = int.toDouble()
    val float: Float = int.toFloat()
    val byte: Byte = int.toByte()
    
    println("Original value: $int")
    println("Converted to Long: $long")
    println("Converted to Double: $double")
    println("Converted to Float: $float")
    println("Converted to Byte: $byte")
    
    // Numeric operations
    val a = 10
    val b = 3
    println("\nNumeric operations:")
    println("$a + $b = ${a + b}")
    println("$a - $b = ${a - b}")
    println("$a * $b = ${a * b}")
    println("$a / $b = ${a / b}")        // Integer division
    println("$a % $b = ${a % b}")        // Modulo
    println("$a / $b.0 = ${a / b.0}")   // Floating-point division
}

Bitwise Operations

kotlin
fun main() {
    val a = 0b1010  // 10 in binary
    val b = 0b1100  // 12 in binary
    
    println("a = $a (${a.toString(2)})")
    println("b = $b (${b.toString(2)})")
    println()
    
    // Bitwise operations
    println("a shl 1 = ${a shl 1} (${(a shl 1).toString(2)})")  // Left shift
    println("a shr 1 = ${a shr 1} (${(a shr 1).toString(2)})")  // Right shift
    println("a ushr 1 = ${a ushr 1}")                           // Unsigned right shift
    println("a and b = ${a and b} (${(a and b).toString(2)})")  // Bitwise AND
    println("a or b = ${a or b} (${(a or b).toString(2)})")     // Bitwise OR
    println("a xor b = ${a xor b} (${(a xor b).toString(2)})")  // Bitwise XOR
    println("a.inv() = ${a.inv()} (${a.inv().toString(2)})")    // Bitwise inversion
}

Characters and Strings

Character Type

kotlin
fun main() {
    val char: Char = 'A'
    val digit: Char = '9'
    val unicode: Char = '\u0041'  // Unicode A
    
    println("Character: $char")
    println("Digit character: $digit")
    println("Unicode character: $unicode")
    
    // Character property checks
    println("\nCharacter properties:")
    println("'$char' is letter: ${char.isLetter()}")
    println("'$digit' is digit: ${digit.isDigit()}")
    println("'$char' is uppercase: ${char.isUpperCase()}")
    println("'$char' to lowercase: ${char.lowercaseChar()}")
    
    // Character conversion
    if (digit.isDigit()) {
        val numericValue = digit.digitToInt()
        println("'$digit' numeric value: $numericValue")
    }
}

String Type

kotlin
fun main() {
    val str1 = "Hello, Kotlin!"
    val str2 = "World"
    
    // String properties
    println("String length: ${str1.length}")
    println("First character: ${str1[0]}")
    println("Last character: ${str1[str1.length - 1]}")
    
    // String methods
    println("\nString methods:")
    println("Uppercase: ${str1.uppercase()}")
    println("Lowercase: ${str1.lowercase()}")
    println("Contains 'Kotlin': ${str1.contains("Kotlin")}")
    println("Starts with 'Hello': ${str1.startsWith("Hello")}")
    println("Ends with '!': ${str1.endsWith("!")}")
    
    // String operations
    println("\nString operations:")
    println("Concatenation: ${str1 + " " + str2}")
    println("Replace: ${str1.replace("Kotlin", "World")}")
    println("Substring: ${str1.substring(0, 5)}")
    println("Split: ${str1.split(" ")}")
    
    // String templates
    val name = "Alice"
    val age = 25
    println("\nString templates:")
    println("Name: $name, Age: $age")
    println("Age next year: ${age + 1}")
    println("Name length: ${name.length}")
}

Raw Strings and Escaping

kotlin
fun main() {
    // Escaped strings
    val escaped = "First line\nSecond line\tTabbed\\\"backslash\\\""
    println("Escaped string:")
    println(escaped)
    
    // Raw strings (triple quotes)
    val raw = """
        |This is a raw string
        |It can contain newlines
        |And "quotes" without escaping
        |Backslashes \ are literal
        |${1 + 1} is still processed
    """.trimMargin()
    
    println("\nRaw string:")
    println(raw)
    
    // Raw string without template processing
    val rawNoTemplate = """
        No template: ${'$'}{1 + 1}
        Use ${'$'}{'$'} to output literal $
    """.trimIndent()
    
    println("\nRaw string without template:")
    println(rawNoTemplate)
}

Boolean Type

kotlin
fun main() {
    val isTrue: Boolean = true
    val isFalse: Boolean = false
    
    // Boolean operations
    println("Boolean operations:")
    println("true && false = ${true && false}")   // Logical AND
    println("true || false = ${true || false}")   // Logical OR
    println("!true = ${!true}")                   // Logical NOT
    
    // Short-circuit evaluation
    fun expensiveOperation(): Boolean {
        println("Executing expensive operation")
        return true
    }
    
    println("\nShort-circuit evaluation:")
    println("false && expensiveOperation() = ${false && expensiveOperation()}")  // Won't execute
    println("true || expensiveOperation() = ${true || expensiveOperation()}")    // Won't execute
}

Arrays

Basic Arrays

kotlin
fun main() {
    // Creating arrays
    val numbers = arrayOf(1, 2, 3, 4, 5)
    val strings = arrayOf("apple", "banana", "cherry")
    val mixed = arrayOf(1, "hello", 3.14, true)  // Any type array
    
    // Typed arrays
    val integers: Array<Int> = arrayOf(1, 2, 3)
    val nullableStrings: Array<String?> = arrayOfNulls(5)
    
    // Creating arrays with constructor
    val squares = Array(5) { i -> i * i }  // [0, 1, 4, 9, 16]
    
    println("Array contents:")
    println("numbers: ${numbers.contentToString()}")
    println("strings: ${strings.contentToString()}")
    println("squares: ${squares.contentToString()}")
    
    // Array access and modification
    println("\nArray operations:")
    println("First element: ${numbers[0]}")
    println("Array length: ${numbers.size}")
    
    numbers[0] = 10
    println("After modification: ${numbers.contentToString()}")
    
    // Array iteration
    println("\nArray iteration:")
    for (number in numbers) {
        print("$number ")
    }
    println()
    
    for ((index, value) in numbers.withIndex()) {
        println("Index $index: $value")
    }
}

Primitive Type Arrays

kotlin
fun main() {
    // Primitive type arrays (avoid boxing overhead)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    val doubleArray = doubleArrayOf(1.1, 2.2, 3.3)
    val booleanArray = booleanArrayOf(true, false, true)
    val charArray = charArrayOf('a', 'b', 'c')
    
    println("Primitive type arrays:")
    println("IntArray: ${intArray.contentToString()}")
    println("DoubleArray: ${doubleArray.contentToString()}")
    println("BooleanArray: ${booleanArray.contentToString()}")
    println("CharArray: ${charArray.contentToString()}")
    
    // Array operations
    println("\nArray operations:")
    println("Sum: ${intArray.sum()}")
    println("Average: ${intArray.average()}")
    println("Max: ${intArray.maxOrNull()}")
    println("Min: ${intArray.minOrNull()}")
    
    // Array transformations
    val doubled = intArray.map { it * 2 }
    val filtered = intArray.filter { it > 2 }
    
    println("Doubled: $doubled")
    println("Filtered > 2: $filtered")
}

Collection Types

Lists

kotlin
fun main() {
    // Immutable list
    val readOnlyList = listOf("apple", "banana", "cherry")
    val emptyList = emptyList<String>()
    val listWithNulls = listOfNotNull("apple", null, "banana", null)
    
    // Mutable list
    val mutableList = mutableListOf("kotlin", "java", "python")
    val arrayList = arrayListOf<String>()
    
    println("List operations:")
    println("Read-only list: $readOnlyList")
    println("List without nulls: $listWithNulls")
    
    // List operations
    mutableList.add("javascript")
    mutableList.remove("java")
    mutableList[0] = "Kotlin"  // Modify element
    
    println("Mutable list: $mutableList")
    
    // List methods
    println("\nList methods:")
    println("Size: ${readOnlyList.size}")
    println("Is empty: ${readOnlyList.isEmpty()}")
    println("Contains 'apple': ${readOnlyList.contains("apple")}")
    println("First element: ${readOnlyList.first()}")
    println("Last element: ${readOnlyList.last()}")
    println("Get index 1: ${readOnlyList.getOrNull(1)}")
    
    // List transformations
    val fruits = listOf("apple", "banana", "cherry")
    val upperCaseFruits = fruits.map { it.uppercase() }
    val longFruits = fruits.filter { it.length > 5 }
    val totalLength = fruits.sumOf { it.length }
    
    println("\nList transformations:")
    println("Uppercase: $upperCaseFruits")
    println("Length > 5: $longFruits")
    println("Total length: $totalLength")
}

Sets

kotlin
fun main() {
    // Immutable set
    val readOnlySet = setOf("apple", "banana", "apple")  // Duplicates are removed
    val emptySet = emptySet<String>()
    
    // Mutable set
    val mutableSet = mutableSetOf("kotlin", "java", "python")
    val hashSet = hashSetOf<String>()
    val linkedHashSet = linkedSetOf<String>()  // Maintains insertion order
    
    println("Set operations:")
    println("Read-only set: $readOnlySet")  // [apple, banana]
    println("Set size: ${readOnlySet.size}")
    
    // Set operations
    mutableSet.add("javascript")
    mutableSet.add("kotlin")  // Duplicate won't be added
    mutableSet.remove("java")
    
    println("Mutable set: $mutableSet")
    
    // Set operations
    val set1 = setOf(1, 2, 3, 4)
    val set2 = setOf(3, 4, 5, 6)
    
    println("\nSet operations:")
    println("set1: $set1")
    println("set2: $set2")
    println("Union: ${set1 union set2}")
    println("Intersection: ${set1 intersect set2}")
    println("Difference: ${set1 - set2}")
    
    // Set checks
    println("\nSet checks:")
    println("set1 contains 2: ${2 in set1}")
    println("set1 contains all [1,2]: ${set1.containsAll(listOf(1, 2))}")
}

Maps

kotlin
fun main() {
    // Immutable map
    val readOnlyMap = mapOf(
        "name" to "Kotlin",
        "version" to "1.9",
        "type" to "Programming Language"
    )
    
    val emptyMap = emptyMap<String, String>()
    
    // Mutable map
    val mutableMap = mutableMapOf<String, Int>()
    val hashMap = hashMapOf<String, String>()
    val linkedHashMap = linkedMapOf<String, String>()  // Maintains insertion order
    
    println("Map operations:")
    println("Read-only map: $readOnlyMap")
    
    // Map operations
    mutableMap["apple"] = 5
    mutableMap["banana"] = 3
    mutableMap["cherry"] = 8
    mutableMap.put("date", 2)  // Equivalent to mutableMap["date"] = 2
    
    println("Mutable map: $mutableMap")
    
    // Map access
    println("\nMap access:")
    println("Count of apple: ${mutableMap["apple"]}")
    println("Count of grape: ${mutableMap["grape"]}")  // null
    println("Count of grape (default): ${mutableMap.getOrDefault("grape", 0)}")
    println("All keys: ${mutableMap.keys}")
    println("All values: ${mutableMap.values}")
    
    // Map iteration
    println("\nMap iteration:")
    for ((fruit, count) in mutableMap) {
        println("$fruit: $count")
    }
    
    // Map transformations
    val doubled = mutableMap.mapValues { (_, value) -> value * 2 }
    val filtered = mutableMap.filter { (_, value) -> value > 3 }
    
    println("\nMap transformations:")
    println("Doubled: $doubled")
    println("Filtered > 3: $filtered")
}

Nullable Types

Nullability Basics

kotlin
fun main() {
    // Non-nullable type
    var name: String = "Kotlin"
    // name = null  // Compilation error
    
    // Nullable type
    var nullableName: String? = "Kotlin"
    nullableName = null  // Allowed
    
    println("Non-nullable type: $name")
    println("Nullable type: $nullableName")
    
    // Safe call operator ?.
    println("Safe call length: ${nullableName?.length}")
    
    // Elvis operator ?:
    val length = nullableName?.length ?: 0
    println("Length (with default): $length")
    
    // Not-null assertion !!
    nullableName = "Kotlin"
    println("Not-null assertion length: ${nullableName!!.length}")  // Use with caution
    
    // Safe cast as?
    val obj: Any = "Hello"
    val str: String? = obj as? String
    val int: Int? = obj as? Int  // null, because cast fails
    
    println("Safe cast to String: $str")
    println("Safe cast to Int: $int")
}

Nullability Handling Patterns

kotlin
// Handling nullable parameters
fun processName(name: String?) {
    // Method 1: Use if check
    if (name != null) {
        println("Processing name: ${name.uppercase()}")  // Smart cast
    } else {
        println("Name is null")
    }
    
    // Method 2: Use let
    name?.let { 
        println("Processing with let: ${it.uppercase()}")
    }
    
    // Method 3: Use Elvis operator
    val processedName = name?.uppercase() ?: "UNKNOWN"
    println("Processed name: $processedName")
}

// Function returning nullable value
fun findUser(id: Int): User? {
    return if (id > 0) User("User$id") else null
}

data class User(val name: String)

fun main() {
    processName("alice")
    processName(null)
    
    val user = findUser(1)
    val invalidUser = findUser(-1)
    
    println("Found user: ${user?.name}")
    println("Invalid user: ${invalidUser?.name}")
}

Type Checking and Casting

Type Checking

kotlin
fun main() {
    val items = listOf("hello", 42, 3.14, true, null)
    
    for (item in items) {
        when (item) {
            is String -> println("String: '$item', length: ${item.length}")
            is Int -> println("Integer: $item, squared: ${item * item}")
            is Double -> println("Double: $item, rounded: ${item.toInt()}")
            is Boolean -> println("Boolean: $item, negated: ${!item}")
            null -> println("Null value")
            else -> println("Unknown type: $item")
        }
    }
}

Type Casting

kotlin
fun main() {
    val obj: Any = "Hello, Kotlin!"
    
    // Unsafe cast as
    try {
        val str = obj as String
        println("Cast successful: $str")
        
        // val int = obj as Int  // Would throw ClassCastException
    } catch (e: ClassCastException) {
        println("Cast failed: ${e.message}")
    }
    
    // Safe cast as?
    val str: String? = obj as? String
    val int: Int? = obj as? Int
    
    println("Safe cast to String: $str")
    println("Safe cast to Int: $int")
    
    // Smart cast
    if (obj is String) {
        // In this scope, obj is smart-cast to String
        println("Smart cast: ${obj.uppercase()}")
    }
}

Generics Basics

Generic Classes and Functions

kotlin
// Generic class
class Box<T>(val value: T) {
    fun get(): T = value
    
    override fun toString(): String = "Box($value)"
}

// Generic function
fun <T> identity(value: T): T = value

fun <T> swap(pair: Pair<T, T>): Pair<T, T> = Pair(pair.second, pair.first)

// Multiple type parameters
fun <K, V> createMap(key: K, value: V): Map<K, V> = mapOf(key to value)

fun main() {
    // Using generic class
    val stringBox = Box("Hello")
    val intBox = Box(42)
    
    println("String box: $stringBox")
    println("Int box: $intBox")
    
    // Using generic functions
    val str = identity("Kotlin")
    val num = identity(100)
    
    println("Identity string: $str")
    println("Identity number: $num")
    
    // Swap pair
    val originalPair = Pair("first", "second")
    val swappedPair = swap(originalPair)
    
    println("Original pair: $originalPair")
    println("Swapped: $swappedPair")
    
    // Create map
    val map = createMap("language", "Kotlin")
    println("Created map: $map")
}

Type Constraints

kotlin
// Upper bound constraint
fun <T : Number> sum(a: T, b: T): Double {
    return a.toDouble() + b.toDouble()
}

// Multiple constraints
interface Printable {
    fun print()
}

class Document : Printable {
    override fun print() = println("Printing document")
}

fun <T> process(item: T) where T : Printable, T : Comparable<T> {
    item.print()
    // Can use both Printable and Comparable methods
}

fun main() {
    // Using constrained generic function
    val intSum = sum(10, 20)
    val doubleSum = sum(3.14, 2.86)
    
    println("Integer sum: $intSum")
    println("Double sum: $doubleSum")
    
    // sum("hello", "world")  // Compilation error: String is not a subtype of Number
}

Type Aliases

kotlin
// Create aliases for complex types
typealias UserMap = Map<String, User>
typealias StringProcessor = (String) -> String
typealias EventHandler<T> = (T) -> Unit

// Create alias for generic class
typealias StringBox = Box<String>

data class User(val name: String, val email: String)

fun main() {
    // Using type aliases
    val users: UserMap = mapOf(
        "1" to User("Alice", "alice@example.com"),
        "2" to User("Bob", "bob@example.com")
    )
    
    val processor: StringProcessor = { it.uppercase() }
    val eventHandler: EventHandler<String> = { println("Event: $it") }
    
    println("User map: $users")
    println("Processor result: ${processor("hello")}")
    eventHandler("Button click")
    
    // Using generic type alias
    val stringBox: StringBox = Box("Hello")
    println("String box: $stringBox")
}

Best Practices

1. Null Safety

kotlin
// Good practice: Use safe calls and Elvis operator
fun processUser(user: User?) {
    val name = user?.name ?: "Unknown"
    val email = user?.email?.lowercase() ?: "no-email"
    println("User: $name, Email: $email")
}

// Avoid overusing not-null assertions
fun badExample(user: User?) {
    // Bad: Overusing !!
    // println("User: ${user!!.name}, Email: ${user!!.email}")
}

2. Collection Selection

kotlin
// Choose appropriate collection type based on requirements
fun main() {
    // Need to maintain order and allow duplicates: List
    val orderItems = listOf("item1", "item2", "item1")
    
    // Need uniqueness: Set
    val uniqueIds = setOf("id1", "id2", "id3")
    
    // Need key-value mapping: Map
    val userPreferences = mapOf("theme" to "dark", "language" to "en")
    
    // Need frequent modifications: Use mutable versions
    val mutableItems = mutableListOf<String>()
}

3. Generic Usage

kotlin
// Use generics to improve code reusability
class Repository<T> {
    private val items = mutableListOf<T>()
    
    fun add(item: T) = items.add(item)
    fun getAll(): List<T> = items.toList()
    fun find(predicate: (T) -> Boolean): T? = items.find(predicate)
}

// Use type constraints for type safety
fun <T : Comparable<T>> findMax(items: List<T>): T? {
    return items.maxOrNull()
}

Next Steps

After understanding Kotlin's data type system, let's learn about declaring and using variables and constants.

Next Chapter: Variables and Constants

Exercises

  1. Create a program demonstrating the use and conversion of all basic data types
  2. Implement a safe division function that handles division by zero and null values
  3. Design a generic cache class that supports storing different types of data
  4. Write functions to demonstrate various collection operations (filtering, mapping, aggregation, etc.)
  5. Create a type-safe configuration manager using type aliases and generics

Content is for learning and research only.