Kotlin Type Conversion

Kotlin type conversion differs significantly from Java’s approach to type casting. While Java allows implicit widening conversions (like converting int to long), Kotlin requires explicit conversion functions for all numeric type transformations. This design choice prioritizes type safety and helps developers avoid unexpected behavior in their applications.

Why Kotlin Doesn’t Support Implicit Type Conversion

Kotlin’s explicit type conversion approach prevents common programming errors that can occur with automatic type promotion. When you attempt to assign a smaller numeric type to a larger one without explicit conversion, Kotlin will generate a compile-time error rather than silently performing the conversion.

val smallNumber: Int = 100
val largeNumber: Long = smallNumber // Compile-time error!

This strict approach ensures that type conversions are intentional and visible in your code, making it easier to track data transformations and debug type-related issues.

Explicit Type Conversion Functions

Kotlin provides a comprehensive set of conversion functions for transforming between different numeric types. Each conversion function follows a consistent naming pattern: to followed by the target type name.

Complete List of Kotlin Type Conversion Functions

FunctionDescriptionExample Usage
toByte()Converts to Byte typemyInt.toByte()
toShort()Converts to Short typemyInt.toShort()
toInt()Converts to Int typemyLong.toInt()
toLong()Converts to Long typemyInt.toLong()
toFloat()Converts to Float typemyInt.toFloat()
toDouble()Converts to Double typemyFloat.toDouble()
toChar()Converts to Char typemyInt.toChar()

Numeric Type Conversion Examples

Here are practical examples of converting between different numeric types:

// Converting Int to other numeric types
val originalInt: Int = 42

val convertedByte: Byte = originalInt.toByte()
val convertedShort: Short = originalInt.toShort()
val convertedLong: Long = originalInt.toLong()
val convertedFloat: Float = originalInt.toFloat()
val convertedDouble: Double = originalInt.toDouble()

println("Original Int: $originalInt")
println("Converted to Byte: $convertedByte")
println("Converted to Long: $convertedLong")
println("Converted to Double: $convertedDouble")

Working with Character Conversions

Character conversion in Kotlin follows ASCII values, making it useful for various text processing scenarios:

// Converting between Char and Int
val letterCode: Int = 65
val letter: Char = letterCode.toChar()
println("ASCII $letterCode represents: $letter") // Output: A

val characterValue: Char = 'Z'
val asciiValue: Int = characterValue.code // Note: .toInt() is deprecated
println("Character '$characterValue' has ASCII value: $asciiValue") // Output: 90

String to Numeric Type Conversion

Converting strings to numeric types is a common requirement in Android development and general Kotlin programming. Kotlin provides safe conversion methods that handle potential parsing errors gracefully.

Safe String Conversion Methods

// Safe string to number conversions
val userInput = "12345"
val invalidInput = "abc123"

// Using safe conversion methods (returns null on failure)
val safeInt: Int? = userInput.toIntOrNull()
val safeDouble: Double? = userInput.toDoubleOrNull()
val failedConversion: Int? = invalidInput.toIntOrNull()

println("Safe Int conversion: $safeInt") // Output: 12345
println("Failed conversion: $failedConversion") // Output: null

// Using direct conversion (throws exception on failure)
try {
    val directInt: Int = userInput.toInt()
    println("Direct conversion: $directInt")
} catch (e: NumberFormatException) {
    println("Conversion failed: ${e.message}")
}

Advanced String Conversion with Radix

Kotlin supports number parsing with different bases (radix), which is particularly useful for hexadecimal or binary conversions:

// Converting strings with different bases
val hexString = "FF"
val binaryString = "1010"
val octalString = "77"

val hexValue: Int = hexString.toInt(16)  // Base 16
val binaryValue: Int = binaryString.toInt(2)   // Base 2
val octalValue: Int = octalString.toInt(8)     // Base 8

println("Hex FF to decimal: $hexValue")     // Output: 255
println("Binary 1010 to decimal: $binaryValue") // Output: 10
println("Octal 77 to decimal: $octalValue")     // Output: 63

Type Casting with ‘as’ and ‘as?’ Operators

Beyond numeric conversions, Kotlin provides powerful type casting operators for working with object hierarchies and nullable types.

Unsafe Cast Operator (as)

The as operator performs explicit type casting but throws a ClassCastException if the cast fails:

fun demonstrateUnsafeCasting() {
    val anyValue: Any = "Hello, Kotlin!"
    
    // Successful cast
    val stringValue: String = anyValue as String
    println("Cast successful: $stringValue")
    
    // This would throw an exception
    try {
        val intValue: Int = anyValue as Int // ClassCastException!
    } catch (e: ClassCastException) {
        println("Cast failed: ${e.message}")
    }
}

Safe Cast Operator (as?)

The as? operator provides a safe alternative that returns null instead of throwing an exception:

fun demonstrateSafeCasting() {
    val mixedValues: List<Any> = listOf("Kotlin", 42, 3.14, true)
    
    for (value in mixedValues) {
        val stringValue: String? = value as? String
        val intValue: Int? = value as? Int
        val doubleValue: Double? = value as? Double
        val booleanValue: Boolean? = value as? Boolean
        
        when {
            stringValue != null -> println("Found string: $stringValue")
            intValue != null -> println("Found integer: $intValue")
            doubleValue != null -> println("Found double: $doubleValue")
            booleanValue != null -> println("Found boolean: $booleanValue")
        }
    }
}

Smart Casting with ‘is’ Operator

Smart casting is one of Kotlin’s most powerful features, automatically casting variables after successful type checks using the is operator.

Basic Smart Casting

fun demonstrateSmartCasting(input: Any) {
    if (input is String) {
        // Smart cast: input is automatically treated as String
        println("String length: ${input.length}")
        println("Uppercase: ${input.uppercase()}")
    } else if (input is Int) {
        // Smart cast: input is automatically treated as Int
        println("Integer value: $input")
        println("Squared: ${input * input}")
    } else if (input is List<*>) {
        // Smart cast: input is automatically treated as List
        println("List size: ${input.size}")
        println("List contents: $input")
    }
}

Smart Casting in When Expressions

Smart casting works seamlessly with when expressions, making code more concise and readable:

fun processDataType(data: Any): String {
    return when (data) {
        is String -> "Text with ${data.length} characters: $data"
        is Int -> "Integer value: $data (binary: ${data.toString(2)})"
        is Double -> "Decimal value: $data (rounded: ${data.toInt()})"
        is List<*> -> "Collection with ${data.size} elements"
        is Boolean -> "Boolean value: ${if (data) "TRUE" else "FALSE"}"
        else -> "Unknown type: ${data::class.simpleName}"
    }
}

Advanced Smart Casting with Logical Operators

Smart casting also works with logical operators, providing more sophisticated type checking:

fun advancedSmartCasting(value: Any?) {
    // Smart casting with null checks
    if (value != null && value is String) {
        println("Non-null string: ${value.uppercase()}")
    }
    
    // Smart casting with logical OR
    if (value is Int || value is Long) {
        // Common supertype is Number
        val numericValue = value as Number
        println("Numeric value: ${numericValue.toDouble()}")
    }
    
    // Smart casting with negation
    if (value !is String) {
        println("Not a string type")
    } else {
        // Smart cast to String in else block
        println("String value: $value")
    }
}

Working with Nullable Types

Nullable type conversion requires special consideration to handle null values safely.

Converting Nullable Types

fun handleNullableConversions() {
    val nullableString: String? = "123"
    val nullValue: String? = null
    
    // Safe conversion with null checks
    val intFromNullable: Int? = nullableString?.toIntOrNull()
    val intFromNull: Int? = nullValue?.toIntOrNull()
    
    println("Converted from nullable: $intFromNullable") // Output: 123
    println("Converted from null: $intFromNull")         // Output: null
    
    // Using Elvis operator for default values
    val safeInt: Int = nullableString?.toIntOrNull() ?: 0
    println("Safe conversion with default: $safeInt")
}

Nullable Type Casting

fun nullableTypeCasting() {
    val nullableAny: Any? = "Kotlin Programming"
    
    // Safe casting with nullable types
    val castedString: String? = nullableAny as? String
    val castedInt: Int? = nullableAny as? Int
    
    println("Casted to String: $castedString")  // Output: Kotlin Programming
    println("Casted to Int: $castedInt")        // Output: null
    
    // Chaining with null-safe operations
    val result = nullableAny as? String ?: "Default Value"
    println("Final result: $result")
}

Practical Android Development Examples

Here are real-world examples of type conversion in Android development contexts:

JSON Data Processing

import org.json.JSONObject

fun processApiResponse(jsonString: String) {
    try {
        val jsonObject = JSONObject(jsonString)
        
        // Safe type conversions for API data
        val userId: Int = jsonObject.optString("user_id").toIntOrNull() ?: -1
        val score: Double = jsonObject.optString("score").toDoubleOrNull() ?: 0.0
        val isActive: Boolean = jsonObject.optString("is_active").toBooleanStrictOrNull() ?: false
        val timestamp: Long = jsonObject.optString("timestamp").toLongOrNull() ?: System.currentTimeMillis()
        
        println("User ID: $userId")
        println("Score: $score")
        println("Active: $isActive")
        println("Timestamp: $timestamp")
        
    } catch (e: Exception) {
        println("JSON parsing error: ${e.message}")
    }
}

SharedPreferences Type Handling

import android.content.SharedPreferences

class PreferencesManager(private val prefs: SharedPreferences) {
    
    fun saveUserSettings(userId: Int, score: Float, isEnabled: Boolean) {
        prefs.edit().apply {
            putString("user_id", userId.toString())
            putString("user_score", score.toString())
            putString("feature_enabled", isEnabled.toString())
            apply()
        }
    }
    
    fun getUserId(): Int {
        return prefs.getString("user_id", "0")?.toIntOrNull() ?: 0
    }
    
    fun getUserScore(): Float {
        return prefs.getString("user_score", "0.0")?.toFloatOrNull() ?: 0.0f
    }
    
    fun isFeatureEnabled(): Boolean {
        return prefs.getString("feature_enabled", "false")?.toBooleanStrictOrNull() ?: false
    }
}

Database Type Conversion

fun convertDatabaseResults(cursor: android.database.Cursor): List<UserProfile> {
    val profiles = mutableListOf<UserProfile>()
    
    while (cursor.moveToNext()) {
        val id = cursor.getString("id").toIntOrNull() ?: 0
        val name = cursor.getString("name") ?: "Unknown"
        val age = cursor.getString("age").toIntOrNull() ?: 0
        val salary = cursor.getString("salary").toDoubleOrNull() ?: 0.0
        val isVerified = cursor.getString("verified").toBooleanStrictOrNull() ?: false
        
        profiles.add(UserProfile(id, name, age, salary, isVerified))
    }
    
    return profiles
}

data class UserProfile(
    val id: Int,
    val name: String,
    val age: Int,
    val salary: Double,
    val isVerified: Boolean
)

Complete Working Example

Here’s a comprehensive example demonstrating various type conversion scenarios in a single application:

fun main() {
    println("=== Kotlin Type Conversion Demo ===\n")
    
    // Numeric conversions
    demonstrateNumericConversions()
    
    // String conversions
    demonstrateStringConversions()
    
    // Object casting
    demonstrateObjectCasting()
    
    // Smart casting
    demonstrateSmartCasting()
    
    // Real-world scenario
    demonstrateRealWorldScenario()
}

fun demonstrateNumericConversions() {
    println("1. Numeric Type Conversions:")
    
    val originalInt = 1000
    val originalDouble = 99.99
    
    println("Original Int: $originalInt")
    println("To Long: ${originalInt.toLong()}")
    println("To Float: ${originalInt.toFloat()}")
    println("To Byte: ${originalInt.toByte()}") // Note: May truncate
    
    println("\nOriginal Double: $originalDouble")
    println("To Int: ${originalDouble.toInt()}")
    println("To Long: ${originalDouble.toLong()}")
    println()
}

fun demonstrateStringConversions() {
    println("2. String to Number Conversions:")
    
    val validNumber = "12345"
    val invalidNumber = "abc123"
    val hexNumber = "1A"
    
    println("Valid string '$validNumber' to Int: ${validNumber.toIntOrNull()}")
    println("Invalid string '$invalidNumber' to Int: ${invalidNumber.toIntOrNull()}")
    println("Hex string '$hexNumber' to Int: ${hexNumber.toIntOrNull(16)}")
    
    // Boolean conversions
    val booleanStrings = listOf("true", "false", "TRUE", "invalid")
    booleanStrings.forEach { str ->
        println("String '$str' to Boolean: ${str.toBooleanStrictOrNull()}")
    }
    println()
}

fun demonstrateObjectCasting() {
    println("3. Object Type Casting:")
    
    val mixedList: List<Any> = listOf("Kotlin", 42, 3.14159, true, null)
    
    mixedList.forEachIndexed { index, item ->
        println("Item $index:")
        
        // Safe casting examples
        when (val safeString = item as? String) {
            null -> print("  Not a string")
            else -> print("  String: '$safeString'")
        }
        
        when (val safeNumber = item as? Number) {
            null -> print(", Not a number")
            else -> print(", Number: $safeNumber")
        }
        
        when (val safeBoolean = item as? Boolean) {
            null -> println(", Not a boolean")
            else -> println(", Boolean: $safeBoolean")
        }
    }
    println()
}

fun demonstrateSmartCasting() {
    println("4. Smart Casting Examples:")
    
    val testValues: List<Any> = listOf(
        "Hello World",
        42,
        listOf(1, 2, 3),
        mapOf("key" to "value")
    )
    
    testValues.forEach { value ->
        val description = when (value) {
            is String -> "String with ${value.length} characters"
            is Int -> "Integer: $value (hex: ${value.toString(16)})"
            is List<*> -> "List with ${value.size} elements: $value"
            is Map<*, *> -> "Map with ${value.size} entries: $value"
            else -> "Unknown type: ${value::class.simpleName}"
        }
        println("  $description")
    }
    println()
}

fun demonstrateRealWorldScenario() {
    println("5. Real-World Scenario - User Input Processing:")
    
    // Simulating user input from a form
    val userInputs = mapOf(
        "age" to "25",
        "salary" to "75000.50",
        "isStudent" to "false",
        "hexColor" to "FF5733",
        "invalidNumber" to "not-a-number"
    )
    
    // Process each input with appropriate type conversion
    val age = userInputs["age"]?.toIntOrNull() ?: 0
    val salary = userInputs["salary"]?.toDoubleOrNull() ?: 0.0
    val isStudent = userInputs["isStudent"]?.toBooleanStrictOrNull() ?: false
    val colorValue = userInputs["hexColor"]?.toIntOrNull(16) ?: 0
    val invalidAttempt = userInputs["invalidNumber"]?.toIntOrNull()
    
    println("  Processed User Data:")
    println("    Age: $age years")
    println("    Salary: $${String.format("%.2f", salary)}")
    println("    Student Status: $isStudent")
    println("    Color Value: #${userInputs["hexColor"]} = $colorValue (decimal)")
    println("    Invalid Conversion: $invalidAttempt")
    
    // Validation example
    when {
        age < 0 -> println("  ⚠️ Invalid age")
        age < 18 -> println("  ✅ Minor")
        age >= 18 -> println("  ✅ Adult")
    }
}

Output

When you run the complete example, you’ll see:

=== Kotlin Type Conversion Demo ===

1. Numeric Type Conversions:
Original Int: 1000
To Long: 1000
To Float: 1000.0
To Byte: -24

Original Double: 99.99
To Int: 99
To Long: 99

2. String to Number Conversions:
Valid string '12345' to Int: 12345
Invalid string 'abc123' to Int: null
Hex string '1A' to Int: 26
String 'true' to Boolean: true
String 'false' to Boolean: false
String 'TRUE' to Boolean: true
String 'invalid' to Boolean: null

3. Object Type Casting:
Item 0:
  String: 'Kotlin', Not a number, Not a boolean
Item 1:
  Not a string, Number: 42, Not a boolean
Item 2:
  Not a string, Number: 3.14159, Not a boolean
Item 3:
  Not a string, Not a number, Boolean: true
Item 4:
  Not a string, Not a number, Not a boolean

4. Smart Casting Examples:
  String with 11 characters
  Integer: 42 (hex: 2a)
  List with 3 elements: [1, 2, 3]
  Map with 1 entries: {key=value}

5. Real-World Scenario - User Input Processing:
  Processed User Data:
    Age: 25 years
    Salary: $75000.50
    Student Status: false
    Color Value: #FF5733 = 16733235 (decimal)
    Invalid Conversion: null
  ✅ Adult

Key Takeaways

Understanding Kotlin type conversion is crucial for effective Android development and general Kotlin programming. Remember these essential points:

  • Explicit conversion is required for all numeric type transformations
  • Safe conversion methods (like toIntOrNull()) prevent runtime exceptions
  • Smart casting automatically handles type conversions after successful is checks
  • Safe cast operator (as?) returns null instead of throwing exceptions
  • String conversions support different number bases (radix) for specialized parsing

Mastering these Kotlin type casting techniques will help you write more robust, type-safe applications while avoiding common pitfalls in data type transformations. Whether you’re processing user input, handling API responses, or managing database operations, proper type conversion ensures your Kotlin applications run smoothly and handle edge cases gracefully.