Skip to content

Variables and Constants

Overview

This chapter provides detailed coverage of declaring, initializing, and using variables and constants in Kotlin. Kotlin distinguishes between mutable and immutable variables using the var and val keywords, which embodies functional programming principles and the concept of immutability.

Variable Declaration

var - Mutable Variables

kotlin
fun main() {
    // Basic variable declaration
    var name = "Kotlin"  // Type inferred as String
    var age: Int = 25    // Explicit type declaration
    var height = 175.5   // Type inferred as Double
    
    println("Initial values:")
    println("Name: $name")
    println("Age: $age")
    println("Height: $height")
    
    // Modify variable values
    name = "Kotlin 1.9"
    age = 26
    height = 176.0
    
    println("\nAfter modification:")
    println("Name: $name")
    println("Age: $age")
    println("Height: $height")
}

val - Immutable Variables

kotlin
fun main() {
    // Immutable variable declaration
    val language = "Kotlin"
    val version = 1.9
    val isStable = true
    
    println("Language info:")
    println("Language: $language")
    println("Version: $version")
    println("Stable: $isStable")
    
    // Attempting to modify val variables causes compilation error
    // language = "Java"  // Compilation error!
    // version = 2.0      // Compilation error!
    
    // val variables must be initialized at declaration or in constructor
    val currentTime: Long
    currentTime = System.currentTimeMillis()  // Deferred initialization
    println("Current time: $currentTime")
    
    // currentTime = System.currentTimeMillis()  // Compilation error: already initialized
}

Type Inference

Automatic Type Inference

kotlin
fun main() {
    // Kotlin can automatically infer types
    val string = "Hello"           // String
    val integer = 42               // Int
    val long = 42L                 // Long
    val double = 3.14              // Double
    val float = 3.14f              // Float
    val boolean = true             // Boolean
    val char = 'A'                 // Char
    
    // Collection type inference
    val list = listOf(1, 2, 3)     // List<Int>
    val map = mapOf("a" to 1)      // Map<String, Int>
    val set = setOf("x", "y")      // Set<String>
    
    println("Type inference examples:")
    println("string: $string (${string::class.simpleName})")
    println("integer: $integer (${integer::class.simpleName})")
    println("list: $list (${list::class.simpleName})")
}

Explicit Type Declaration

kotlin
fun main() {
    // When you need explicit types
    val number: Number = 42        // Parent type
    val any: Any = "Hello"         // Top-level type
    val nullable: String? = null   // Nullable type
    
    // Empty collections need explicit types
    val emptyList: List<String> = emptyList()
    val emptyMap: Map<String, Int> = emptyMap()
    
    // Function types
    val function: (Int, Int) -> Int = { a, b -> a + b }
    
    println("Explicit type declarations:")
    println("number: $number")
    println("any: $any")
    println("nullable: $nullable")
    println("function result: ${function(5, 3)}")
}

Constants

Compile-Time Constants (const)

kotlin
// Top-level constants
const val APP_NAME = "MyKotlinApp"
const val VERSION_CODE = 1
const val PI = 3.14159

class MathUtils {
    companion object {
        // Constants in companion object
        const val E = 2.71828
        const val GOLDEN_RATIO = 1.61803
    }
}

fun main() {
    println("Application constants:")
    println("App name: $APP_NAME")
    println("Version code: $VERSION_CODE")
    println("PI: $PI")
    println("E: ${MathUtils.E}")
    println("Golden ratio: ${MathUtils.GOLDEN_RATIO}")
}

Runtime Constants

kotlin
class Configuration {
    companion object {
        // Constants determined at runtime
        val START_TIME = System.currentTimeMillis()
        val USER_HOME = System.getProperty("user.home")
        val RANDOM_ID = java.util.UUID.randomUUID().toString()
    }
}

fun main() {
    println("Runtime constants:")
    println("Start time: ${Configuration.START_TIME}")
    println("User home: ${Configuration.USER_HOME}")
    println("Random ID: ${Configuration.RANDOM_ID}")
}

Variable Scope

Local Variables

kotlin
fun demonstrateScope() {
    val outerVariable = "outer variable"
    
    if (true) {
        val innerVariable = "inner variable"
        println("Inner scope can access: $outerVariable")
        println("Inner variable: $innerVariable")
    }
    
    // println(innerVariable)  // Compilation error: cannot access inner variable
    
    // Loop scope
    for (i in 1..3) {
        val loopVariable = "loop variable $i"
        println(loopVariable)
    }
    
    // println(loopVariable)  // Compilation error: cannot access loop variable
}

fun main() {
    demonstrateScope()
}

Class Member Variables

kotlin
class Person {
    // Instance properties
    var name: String = ""
    val id: String = generateId()
    
    // Private property
    private var age: Int = 0
    
    // Protected property
    protected var email: String = ""
    
    // Internal property
    internal var department: String = ""
    
    // Late-initialized property
    lateinit var address: String
    
    // Lazy property
    val expensiveProperty: String by lazy {
        println("Computing expensive property...")
        "Expensive computation result"
    }
    
    companion object {
        // Class properties (static)
        var totalPersons = 0
        const val SPECIES = "Homo sapiens"
    }
    
    private fun generateId(): String {
        return "PERSON_${System.currentTimeMillis()}"
    }
    
    fun setAge(newAge: Int) {
        if (newAge >= 0) {
            age = newAge
        }
    }
    
    fun getAge() = age
}

fun main() {
    val person = Person()
    person.name = "Alice"
    person.setAge(25)
    person.address = "123 Main St"  // lateinit property must be initialized before use
    
    println("Person info:")
    println("Name: ${person.name}")
    println("ID: ${person.id}")
    println("Age: ${person.getAge()}")
    println("Address: ${person.address}")
    println("Expensive property: ${person.expensiveProperty}")  // Computed on first access
    println("Species: ${Person.SPECIES}")
}

Property Accessors

Custom Getters and Setters

kotlin
class Rectangle(width: Double, height: Double) {
    var width: Double = width
        set(value) {
            if (value > 0) {
                field = value
            } else {
                throw IllegalArgumentException("Width must be greater than 0")
            }
        }
    
    var height: Double = height
        set(value) {
            if (value > 0) {
                field = value
            } else {
                throw IllegalArgumentException("Height must be greater than 0")
            }
        }
    
    // Computed property (getter only)
    val area: Double
        get() = width * height
    
    val perimeter: Double
        get() = 2 * (width + height)
    
    // Property with backing field
    var name: String = ""
        get() = field.uppercase()
        set(value) {
            field = value.trim()
        }
}

fun main() {
    val rectangle = Rectangle(5.0, 3.0)
    
    println("Rectangle info:")
    println("Width: ${rectangle.width}")
    println("Height: ${rectangle.height}")
    println("Area: ${rectangle.area}")
    println("Perimeter: ${rectangle.perimeter}")
    
    rectangle.name = "  my rectangle  "
    println("Name: '${rectangle.name}'")  // Automatically uppercased and trimmed
    
    // Modify dimensions
    rectangle.width = 6.0
    rectangle.height = 4.0
    println("New area: ${rectangle.area}")
    
    try {
        rectangle.width = -1.0  // Will throw exception
    } catch (e: IllegalArgumentException) {
        println("Error: ${e.message}")
    }
}

Late Initialization

lateinit Modifier

kotlin
class DatabaseService {
    lateinit var connection: String
    
    fun initialize() {
        connection = "Database connection established"
    }
    
    fun isInitialized(): Boolean {
        return ::connection.isInitialized
    }
    
    fun getConnection(): String {
        if (!::connection.isInitialized) {
            throw IllegalStateException("Connection not initialized")
        }
        return connection
    }
}

fun main() {
    val service = DatabaseService()
    
    println("Initialization state: ${service.isInitialized()}")
    
    try {
        service.getConnection()  // Will throw exception
    } catch (e: IllegalStateException) {
        println("Error: ${e.message}")
    }
    
    service.initialize()
    println("After initialization: ${service.isInitialized()}")
    println("Connection: ${service.getConnection()}")
}

lazy Delegate

kotlin
class ExpensiveResource {
    // Lazy property
    val expensiveData: List<String> by lazy {
        println("Loading expensive data...")
        Thread.sleep(1000)  // Simulate time-consuming operation
        listOf("Data1", "Data2", "Data3")
    }
    
    // Thread-safe lazy loading
    val threadSafeData: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        "Thread-safe data"
    }
    
    // Non-thread-safe lazy loading (better performance)
    val nonThreadSafeData: String by lazy(LazyThreadSafetyMode.NONE) {
        "Non-thread-safe data"
    }
}

fun main() {
    val resource = ExpensiveResource()
    
    println("Resource created, but data not yet loaded")
    
    // Data is loaded on first access
    println("First access: ${resource.expensiveData}")
    
    // Subsequent access uses cached value
    println("Second access: ${resource.expensiveData}")
}

Delegated Properties

Standard Delegates

kotlin
import kotlin.properties.Delegates

class User {
    // Observable property
    var name: String by Delegates.observable("Initial value") { property, oldValue, newValue ->
        println("${property.name} changed from '$oldValue' to '$newValue'")
    }
    
    // Vetoable property
    var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
        println("Attempting to change ${property.name} from $oldValue to $newValue")
        newValue >= 0  // Only allow non-negative values
    }
    
    // Map delegate
    private val map = mutableMapOf<String, Any?>()
    var email: String by map
    var phone: String by map
}

fun main() {
    val user = User()
    
    println("=== Observable property ===")
    user.name = "Alice"
    user.name = "Bob"
    
    println("\n=== Vetoable property ===")
    user.age = 25
    user.age = -5  // Will be rejected
    println("Final age: ${user.age}")
    
    println("\n=== Map delegate ===")
    user.email = "user@example.com"
    user.phone = "123-456-7890"
    println("Email: ${user.email}")
    println("Phone: ${user.phone}")
}

Custom Delegates

kotlin
import kotlin.reflect.KProperty

class LoggingDelegate<T>(private var value: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("Getting property ${property.name} = $value")
        return value
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        println("Setting property ${property.name} from $value to $newValue")
        value = newValue
    }
}

class Example {
    var data: String by LoggingDelegate("Initial value")
}

fun main() {
    val example = Example()
    
    println("Reading data: ${example.data}")
    example.data = "New value"
    println("Reading again: ${example.data}")
}

Variable Naming Conventions

Naming Rules

kotlin
class NamingConventions {
    // Good naming examples
    val userName = "john_doe"           // camelCase
    val isActive = true                 // Boolean with is prefix
    val hasPermission = false           // Boolean with has prefix
    val canEdit = true                  // Boolean with can prefix
    
    // Constant naming
    companion object {
        const val MAX_RETRY_COUNT = 3   // UPPER_SNAKE_CASE
        const val API_BASE_URL = "https://api.example.com"
        const val DEFAULT_TIMEOUT = 5000L
    }
    
    // Private properties
    private val _internalData = mutableListOf<String>()
    val internalData: List<String> get() = _internalData
    
    // Naming to avoid
    // val d = "data"              // Too short, unclear meaning
    // val userData123 = "user"    // Avoid numeric suffixes
    // val user_name = "john"      // Avoid underscores in Kotlin
}

Best Practices

1. Prefer val

kotlin
fun main() {
    // Good practice: Prefer val
    val items = mutableListOf<String>()  // Reference is immutable, but content is mutable
    items.add("item1")
    items.add("item2")
    
    // Use var only when necessary
    var counter = 0
    for (item in items) {
        counter++
        println("$counter: $item")
    }
}

2. Use Type Inference Wisely

kotlin
fun main() {
    // Good practice: Let compiler infer obvious types
    val name = "Kotlin"
    val count = 42
    val items = listOf("a", "b", "c")
    
    // Explicitly specify types when needed
    val number: Number = 42  // Need parent type
    val nullable: String? = getName()  // Nullable type
    val empty: List<String> = emptyList()  // Empty collection
}

fun getName(): String? = null

3. Use Late Initialization Appropriately

kotlin
class Service {
    // Use lazy for expensive computations
    private val expensiveResource by lazy {
        createExpensiveResource()
    }
    
    // Use lateinit for properties that must be initialized after construction
    lateinit var configuration: Configuration
    
    private fun createExpensiveResource(): String {
        // Simulate expensive operation
        return "Expensive resource"
    }
}

data class Configuration(val setting: String)

4. Use Meaningful Variable Names

kotlin
// Good naming
fun calculateTotalPrice(items: List<Item>, taxRate: Double): Double {
    val subtotal = items.sumOf { it.price }
    val taxAmount = subtotal * taxRate
    val totalPrice = subtotal + taxAmount
    return totalPrice
}

// Naming to avoid
fun calc(l: List<Item>, r: Double): Double {
    val s = l.sumOf { it.price }
    val t = s * r
    val total = s + t
    return total
}

data class Item(val price: Double)

Next Steps

After mastering variables and constants, let's learn about various operators in Kotlin.

Next Chapter: Operators

Exercises

  1. Create a class demonstrating all types of property declarations and accessors
  2. Implement a configuration manager class using different initialization methods
  3. Design a counter class that uses delegated properties to track access count
  4. Write a program to demonstrate variable scope rules
  5. Create a custom delegate to implement encrypted property storage

Content is for learning and research only.