val vs var

En Kotlin toda variable se declara con val o var. La diferencia es simple pero importante:

// val — inmutable, no se puede reasignar (equivale a final en Java)
val nombre = "Carlos"
nombre = "Pedro"  // ERROR de compilación

// var — mutable, se puede reasignar
var edad = 30
edad = 31  // OK

// La regla práctica: empezá siempre con val.
// Cambiá a var solo cuando necesitás reasignar.
// El compilador te avisa si un var nunca se reasigna.

Por qué preferir valUsar val por default hace el código más predecible y fácil de razonar — sabés que ese valor no va a cambiar. En Android, la inmutabilidad también ayuda a evitar bugs en código concurrente.

Tipos básicos

Kotlin tiene los tipos primitivos que conocés, pero todos son objetos — no hay distinción entre primitivos y objetos como en Java:

// Números enteros
val edad: Int = 30
val poblacion: Long = 8_000_000_000L  // _ como separador visual
val byte: Byte = 127
val corto: Short = 32767

// Números decimales
val precio: Double = 99.99   // 64-bit, más preciso
val temperatura: Float = 36.6f  // 32-bit, sufijo f obligatorio

// Otros
val activo: Boolean = true
val inicial: Char = 'K'
val texto: String = "Hola Kotlin"

En AndroidEl compilador convierte los tipos Kotlin a primitivos de Java cuando puede, así que no hay overhead de performance por usar Int en lugar de int.

Inferencia de tipos

Kotlin puede inferir el tipo a partir del valor asignado. No necesitás declararlo explícitamente en la mayoría de los casos:

// Con tipo explícito
val nombre: String = "Carlos"
val edad: Int = 30

// Con inferencia — el compilador deduce el tipo
val nombre = "Carlos"   // String
val edad = 30           // Int
val precio = 99.99      // Double
val activo = true       // Boolean

// Cuándo escribir el tipo explícito:
// 1. Cuando la variable se declara sin valor inicial
var resultado: Int          // necesitás el tipo si no asignás valor
resultado = calcular()

// 2. Cuando querés ser explícito por claridad
val timeout: Long = 5000    // deja claro que es Long, no Int

Null safety — la feature más importante de Kotlin

En Java, cualquier referencia puede ser null y el compilador no te avisa. Resultado: NullPointerException en runtime, el bug más común en Android.

En Kotlin, el sistema de tipos distingue entre tipos que pueden ser null y los que no. Esta distinción se hace en tiempo de compilación — si tu código puede causar un NPE, no compila.

// Tipo no-nullable — NUNCA puede ser null
var nombre: String = "Carlos"
nombre = null  // ERROR de compilación

// Tipo nullable — puede ser null, se marca con ?
var apellido: String? = "Pensa"
apellido = null  // OK

// Para usar un tipo nullable, el compilador te obliga a manejar el null
fun mostrarLongitud(texto: String?) {
    // Esto NO compila — texto puede ser null
    println(texto.length)  // ERROR

    // Necesitás manejar el caso null explícitamente
    if (texto != null) {
        println(texto.length)  // OK: el compilador sabe que no es null acá
    }
}

El contrato de null safetySi una función recibe un parámetro String (sin ?), tenés garantizado por el compilador que nunca va a ser null. No necesitás defensive null checks. Esto elimina una clase entera de bugs y hace el código más limpio.

Operadores para trabajar con nullables

val nombre: String? = obtenerNombre()

// 1. Safe call operator ?.
// Ejecuta la operación solo si no es null, retorna null si lo es
val longitud = nombre?.length   // Int? — puede ser null

// Encadenamiento de safe calls
val ciudad = usuario?.direccion?.ciudad  // null si cualquiera es null

// 2. Elvis operator ?:
// "Si es null, usá este valor alternativo"
val longitud = nombre?.length ?: 0      // Int — nunca null
val ciudad = usuario?.ciudad ?: "Sin ciudad"

// 3. Non-null assertion !!
// "Estoy SEGURO de que no es null — si me equivoco, que crashee"
val longitud = nombre!!.length  // lanza KotlinNullPointerException si es null
// Usalo solo cuando sabés con certeza que no puede ser null
// y el compilador no puede inferirlo solo

// 4. let — ejecutar un bloque solo si no es null
nombre?.let { n ->
    println("El nombre tiene ${n.length} caracteres")
    // n es String (no nullable) dentro del bloque
}

// 5. Elvis con return/throw — muy útil en funciones
fun procesarNombre(nombre: String?) {
    val n = nombre ?: return  // sale de la función si es null
    // A partir de acá, n es String (no nullable)
    println(n.uppercase())
}

String templates

Kotlin permite interpolar valores directamente dentro de strings con $:

val nombre = "Carlos"
val edad = 30

// Interpolación simple con $
val mensaje = "Hola $nombre"          // "Hola Carlos"

// Expresión con ${}
val info = "Tenés ${edad + 1} años"   // "Tenés 31 años"
val mayus = "Nombre: ${nombre.uppercase()}"  // "Nombre: CARLOS"

// Multilínea con triple comilla
val html = """
    

$nombre

Edad: $edad

""".trimIndent() // Antes en Java: // String mensaje = "Hola " + nombre + ", tenés " + edad + " años"; // Ahora: val mensajeKotlin = "Hola $nombre, tenés $edad años"