Declaración de variables
En la mayoría de los lenguajes las variables son simples contenedores de datos. En Kotlin, en cambio, son propiedades que pueden incluir lógica de acceso, validación y encapsulamiento.
Este enfoque convierte incluso a las declaraciones más simples en herramientas poderosas de diseño, especialmente al crear bibliotecas reutilizables.
En esta lección aprenderás a:
- Declarar variables con
val
val
var
var
- Encapsular el acceso a los datos mediante getters y setters personalizados.
- Proteger las invariantes internas de tus estructuras sin perder expresividad hacia quienes usan tu API.
Lo harás con un enfoque práctico, apoyado en buenas prácticas de diseño, para escribir código claro, seguro y mantenible.
Declaración de variables
En Kotlin existen dos formas principales de declarar variables, según necesites inmutabilidad o mutabilidad:
-
val
val
-
var
var
(val|var) nombreVariable: Tipo = valor
// Examples:
val pi: Double = 3.14159
var counter: Int = 0
En variables locales o privadas puedes omitir el tipo si el compilador lo infiere a partir del valor asignado. Esto mejora la legibilidad sin perder seguridad de tipos.
En propiedades y funciones públicas de una biblioteca, en cambio, es recomendable declarar explícitamente el tipo : hace más clara la API, reduce la fragilidad frente a cambios y facilita el mantenimiento a largo plazo.
val
val
Sólo lectura: val
val
Usa
cuando la referencia de
la variable no cambiará después de su asignación. Una vez inicializada, no
podrás volver a asignarle otro valor.
val
val
val
val
val hero = "Mr. Incredible"
hero = "Elastigirl" // Error: no se puede reasignar un 'val'
val sidekick: String
sidekick = "Dash" // Correcto: asignación diferida (una sola vez)
sidekick = "Violet" // Error: la referencia no puede cambiar
-
hero
hero
val
val
-
sidekick
sidekick
val
val
Estas variables son referencias inmutables o propiedades de solo lectura: su referencia no puede cambiar una vez asignada. Sin embargo, el valor al que apuntan puede ser mutable (por ejemplo, una lista modificable).
Más adelante veremos las constantes en tiempo de compilación
declaradas con
, que deben conocerse en
compilación. Por ahora, basta con distinguir entre
const val
const val
y var
var
.
val
val
var
var
Lectura y escritura: var
var
Usa
cuando necesites cambiar el valor o reasignar la referencia de una variable después de declararla.
var
var
var codename = "Mr. Incredible"
codename = "Elastigirl" // ✅ Reasignación: la referencia cambia
var energy = 10
energy = energy + 5 // ✅ Suma explícita
energy += 5 // ✅ Operador compuesto
energy++ // ✅ Incremento en 1
// También puedes reasignar a otro objeto del mismo tipo
var gadgets = listOf("Tracker", "Comms")
gadgets = listOf("Grapple", "Inviso-Goggles") // ✅ Nueva lista
-
codename
codename
-
energy
energy
+=
+=
++
++
- En
gadgets
gadgets
En Kotlin, declarar una variable con
no garantiza que el contenido sea inmutable:
significa que la referencia no puede cambiar. Si el
objeto apuntado es mutable, puedes modificar su estado.
val
val
val
val
fija la referencia, no necesariamente
el estado interno
val lista = mutableListOf(1, 2, 3)
lista.add(4) // válido: cambia el contenido, no la referencia
lista[0] = 99 // también válido
val otra = listOf(1, 2, 3) // Lista de solo lectura (interfaz), no garantiza inmutabilidad profunda
otra.add(4) // no compila: la API no expone mutación
lista = mutableListOf(5, 6, 7) // Error: no se puede reasignar un 'val'
En lenguajes como Rust, la inmutabilidad por defecto sí aplica al valor; para mutar necesitas declarar explícitamente con
:
mut
mut
let mut lista = vec![1, 2, 3];
lista.push(4); // permitido gracias a 'mut'
let lista_fija = vec![1, 2, 3];
lista_fija.push(4); // error: no se puede modificar un valor inmutable
val
val
siempre que sea posible
Prefiere val
val
Siempre que puedas, usa
en lugar de val
val
. La inmutabilidad mejora la legibilidad, facilita el mantenimiento
del código y
hace más sencillo el razonamiento formal sobre su comportamiento.
Recurre a var
var
solo cuando el valor deba cambiar con el tiempo.
var
var
val
val
que con
var
var
// Mejor con val: expresa intención clara, el valor no cambia (más seguro y legible)
val basePower = 42
val bonus = 8
val total = basePower + bonus
// Menos claro con var: sugiere que el valor podría seguir cambiando
var total = 42
total += 8
En el primer caso,
es un resultado fijo y
predecible. En el segundo, el uso de total
total
puede
dar la impresión de que
var
var
seguirá cambiando, aunque no sea así.
total
total
var
var
?
¿Cuándo usar var
var
Aunque
debería ser tu primera opción, hay casos donde val
val
resulta más apropiado:
var
var
- Cuando una variable representa el estado interno mutable de una clase.
- Cuando necesitas acumular resultados de forma incremental en un bucle o función.
- Cuando implementas algoritmos imperativos donde el cambio de estado es más natural y legible.
- Cuando escribes tests o scripts en los que la simplicidad y claridad pesan más que la inmutabilidad.
Propiedades en Kotlin: más que simples campos
En Kotlin,
y val
val
van más
allá de declarar simples campos: definen propiedades con acceso
controlado mediante getters y setters.
var
var
Esto permite encapsular lógica sin perder expresividad:
// Propiedad de solo lectura (val)
val soloLectura: Tipo
get() = campoPersonalizado // lógica al obtener el valor
// Propiedad de lectura y escritura (var)
var lecturaEscritura: Tipo
get() = campoPersonalizado
set(value) {
// lógica antes de asignar
field = value
}
-
val
val
-
var
var
- Puedes personalizar los métodos para añadir validación, transformación u otros efectos colaterales.
En el contexto de bibliotecas, esto permite exponer APIs seguras que muestran información de forma controlada, mientras el diseño interno conserva la flexibilidad necesaria para evolucionar o proteger invariantes.
Ejemplo práctico: Listas inmutables con campos de respaldo
Al diseñar una biblioteca, es común tener estructuras internas mutables
que
no deben exponerse directamente. En Kotlin, esto se
resuelve fácilmente mediante un campo de respaldo con convención
de prefijo
:
_
_
private val _party: MutableList<String> = mutableListOf("Balthier", "Vaan")
val party: List<String>
get() = _party
fun addMember(name: String) {
_party.add(name) // mutación interna permitida
}
-
_party
_party
-
party
party
- Al devolver
List<String>
List<String>
- Este patrón garantiza que la API pública sea segura y predecible.
Es una práctica común en bibliotecas bien diseñadas: exponer lo mínimo necesario mientras se protegen las invariantes internas.
Ejemplo práctico: Encapsular lógica de asignación en el setter
Cuando diseñamos una biblioteca, puede ser necesario que ciertos valores no se asignen directamente, sino que pasen por validaciones o transformaciones. Esto se logra mediante un setter personalizado.
private val semverRegex = Regex("""^\d+\.\d+\.\d+$""")
var version: String = "1.0.0"
set(value) {
val v = value.trim()
require(semverRegex.matches(v)) {
"Version must be in the format X.Y.Z (integers)."
}
field = v
}
-
version
version
- El patrón
semverRegex
semverRegex
X.Y.Z
X.Y.Z
^
^
$
$
-
require(...)
require(...)
IllegalArgumentException
IllegalArgumentException
Este patrón permite crear APIs intuitivas pero seguras, combinando accesibilidad con control sobre las invariantes internas.
require()
require()
, check()
check()
y error()
error()
Funciones de validación estándar: require()
require()
check()
check()
error()
error()
-
require(Boolean, () -> Any): Unit
require(Boolean, () -> Any): Unit
IllegalArgumentException
IllegalArgumentException
-
check(Boolean, () -> Any): Unit
check(Boolean, () -> Any): Unit
IllegalStateException
IllegalStateException
-
error(Any): Nothing
error(Any): Nothing
IllegalStateException
IllegalStateException
Una propiedad puede tener un getter público y un setter privado. Esto permite que el valor se lea desde fuera, pero solo se modifique dentro del mismo contexto (clase, objeto, interfaz o nivel superior).
var score: Int = 0
private set // setter privado
Supón que estás diseñando una biblioteca para un motor de juego. Quieres exponer el nivel de dificultad actual para que otras personas puedan consultarlo, pero solo el motor debe poder modificarlo.
Declara una propiedad
que:difficultyLevel
difficultyLevel
- Sea de tipo
String
String
- Permita valores como
"easy"
"easy"
"normal"
"normal"
"hard"
"hard"
- Pueda leerse públicamente desde cualquier parte.
- Solo pueda modificarse desde dentro del módulo, y con validación.
Hints
in
in
para verificar que un valor se encuentre dentro de un conjunto de la forma x in setOf(...)
x in setOf(...)
.
Solución
var difficultyLevel: String = "normal"
private set(value) {
require(value in setOf("easy", "normal", "hard")) {
"Difficulty level must be one of: easy, normal, hard."
}
field = value
}