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"