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