L2: Variables, Tipos y la Verdad sobre Null Safety
El Error del Billón de Dólares
Sección titulada «El Error del Billón de Dólares»En 1965, el científico de la computación Sir Tony Hoare introdujo el concepto de la “referencia nula” (null) al diseñar el lenguaje ALGOL W. Décadas después, él mismo se disculpó públicamente llamándolo su “error del billón de dólares”, porque ha causado incontables caídas de sistemas, vulnerabilidades y horas de depuración en lenguajes como Java y C++.
El famoso NullPointerException (NPE) ocurre cuando intentas interactuar con algo que creías que existía, pero en realidad está vacío (null). Imagina intentar encender un auto, pero el motor literalmente no existe dentro del chasis.
Kotlin fue diseñado desde cero para solucionar este problema de raíz.
Las Cajas: val vs var
Sección titulada «Las Cajas: val vs var»En Kotlin, puedes imaginar que las variables son cajas donde guardas datos. Hay dos tipos fundamentales de cajas:
La caja abierta: var (Variable)
Sección titulada «La caja abierta: var (Variable)»Es una caja normal. Puedes meter un dato, sacarlo, y meter otro dato diferente en cualquier momento. Su contenido puede mutar o variar.
// 'var' viene de Variable. Al usar var, le decimos al compilador de Kotlin// que esta caja está diseñada para ser abierta y reasignada en el futuro.var age = 25// Como age es un 'var', es perfectamente legal cambiar hacia dónde apunta.age = 26La caja sellada con cinta: val (Value)
Sección titulada «La caja sellada con cinta: val (Value)»Es una caja donde guardas un dato y la sellas. Una vez que le asignas un valor, no puedes cambiar la caja para que apunte a otro objeto. Esto se conoce como una variable de Solo Lectura (Read-Only).
// 'val' viene de Value. Al usar val, creamos un enlace de Solo Lectura.// Le decimos a Kotlin: "Una vez que metas el dato aquí, sella la caja".// Esto hace que el código sea más predecible y seguro.val username = "jesusdmedinac"// ❌ ERROR DE COMPILACIÓN: Val cannot be reassigned.// Kotlin bloquea esta acción porque rompimos la promesa del 'val'.username = "guest"🧠 Puente Conceptual: Variables e Inmutabilidad en tu Lenguaje
Sección titulada «🧠 Puente Conceptual: Variables e Inmutabilidad en tu Lenguaje»valen Kotlin se asemeja aconsten JavaScript/TypeScript: la referencia no puede ser reasignada.- Sin embargo, en TypeScript
const arr = ["A", "B"]permitearr.push("C")sin ninguna restricción. En Kotlin diferenciamos explícitamente entre la interfaz inmutable de solo lectura (List, creada conlistOf) y la interfaz mutable (MutableList, creada conmutableListOf). varequivale directamente aleten TS.
- En Python no existen constantes a nivel de compilador; solo existe la convención de escribir nombres en mayúsculas (
MAX_USERS = 100), pero técnicamente pueden ser reasignadas. - Una
Listde solo lectura en Kotlin equivale conceptualmente a unatupleen Python (no permite agregar elementos). - Una
MutableListde Kotlin equivale a unalistestándar de Python (lista.append()).
- En C#,
valequivale a una variable local no reasignable o a campos marcados conreadonly. - Una
List<T>de Kotlin equivale a la interfazIReadOnlyList<T>de C#. - Una
MutableList<T>de Kotlin equivale a la clase concretaList<T>de C#. varen C# es solo inferencia de tipo; en Kotlinvarsignifica explícitamente que la variable es mutable.
valequivale a declarar una variable local con el modificadorfinal(final String username = "...";).listOf()de Kotlin equivale aList.of()(introducido en Java 9) oCollections.unmodifiableList().mutableListOf()equivale al clásiconew ArrayList<>().
Null Safety: Agitando las cajas transparentes
Sección titulada «Null Safety: Agitando las cajas transparentes»El sistema de tipos de Kotlin distingue entre referencias que pueden ser nulas (cajas que podrían estar vacías) y las que no pueden serlo (cajas que te garantizan que tienen algo dentro).
Para decirle a Kotlin que una caja podría contener null, le agregamos un signo de interrogación ? al tipo:
// Al declarar explícitamente el tipo como 'String' (sin el signo ?),// le prometemos a Kotlin que esta caja NUNCA estará vacía.var name: String = "Jesus"// ❌ ERROR: Kotlin nos protege de meter un null aquí, porque// romperíamos la promesa del tipo no-nulo.name = null
// Al agregar el '?', creamos un tipo Nullable (String?).// Es como decirle a Kotlin: "Soy consciente de que esto podría estar vacío en el futuro".var nullableName: String? = "Daniel"// ✅ Perfecto. Como el tipo es String?, Kotlin permite que la caja se vacíe.nullableName = nullFormas de agitar la caja (Validar Nulabilidad)
Sección titulada «Formas de agitar la caja (Validar Nulabilidad)»Kotlin nos ofrece un arsenal completo para manejar tipos nulos de forma segura. Explorémoslos del más básico al más peligroso.
1. El clásico if != null (Smart Cast)
Sección titulada «1. El clásico if != null (Smart Cast)»La forma más directa y conocida por los programadores de Java es usar un bloque condicional.
if (nullableName != null) { // Aquí adentro, Kotlin automáticamente promueve nullableName a un tipo seguro (String normal) // porque ya demostraste matemáticamente que no es nulo. A esto se le llama Smart Cast. println(nullableName.length)}2. Safe Call (?.)
Sección titulada «2. Safe Call (?.)»Ejecuta la acción solo si el valor no es nulo. Si la caja está vacía, simplemente devuelve null en lugar de explotar.
// Si nullableName es null, la variable 'length' valdrá null. ¡El programa no colapsa!val length = nullableName?.length3. Operador Elvis (?:)
Sección titulada «3. Operador Elvis (?:)»Se llama así porque parece el copete de Elvis Presley volteado de lado ?:. Se usa en combinación con el Safe Call para proveer un valor por defecto en caso de que la caja esté vacía.
// "Usa el nombre, pero si es nulo, entonces usa 'Invitado'"val finalName = nullableName ?: "Invitado"4. Not-Null Assertion (!!)
Sección titulada «4. Not-Null Assertion (!!)»El botón de autodestrucción. Le dice a Kotlin: “Confía en mí, estoy 100% seguro de que esto no es nulo, y si me equivoco, asumo la responsabilidad de que el programa explote”.
// 💥 ADVERTENCIA: Si nullableName resulta ser null en tiempo de ejecución,// esta línea lanzará un NullPointerException y matará la aplicación.val dangerLength = nullableName!!.length🧠 Puente Conceptual: Null Safety en tu Lenguaje
Sección titulada «🧠 Puente Conceptual: Null Safety en tu Lenguaje»- En TypeScript existe la regla
strictNullChecksentsconfig.json. Si está activada,string | nullse comporta prácticamente igual aString?. - El Safe Call (
?.) y el Elvis (?:) de Kotlin son equivalentes al Optional Chaining (?.) y al Nullish Coalescing (??) de JavaScript moderno. - Los Smart Casts de Kotlin equivalen a los Type Guards de TypeScript (
if (typeof x === "string")). - El operador
!!de Kotlin es el gemelo exacto del operador!(Non-null assertion) de TypeScript (obj!.prop).
- En Python tradicional, cualquier variable puede contener
Noneen cualquier momento sin advertencias en tiempo de ejecución. - Para modelar seguridad contra nulos, en Python moderno se usan anotaciones de tipo con
Optional[str]ostr | Nonede la libreríatyping. - La diferencia clave es que en Python la verificación depende de linters externos opcionales (
mypyo PyRight), mientras que en Kotlin el propio compilador bloquea la compilación si no manejas los casos nulos.
- A partir de C# 8.0, Microsoft introdujo los Nullable Reference Types (
string?). La sintaxis y la filosofía de diseño son idénticas a las de Kotlin. - El operador
!!de Kotlin cumple exactamente el mismo rol que el operador de supresión de advertencias!de C# (null-forgiving operator). - El operador Elvis
?:de Kotlin equivale al operador null-coalescing??de C#.
- En Java tradicional, cualquier objeto puede ser
nullen cualquier momento (nulabilidad ambiental no controlada por el compilador). - Aunque Java 8 introdujo
Optional<T>, este requiere instanciar un objeto envoltorio en memoria (heap allocation). En Kotlin,String?es una verificación pura en tiempo de compilación: cero sobrecarga de memoria en la JVM.
Detrás de cámaras: Tipos de Plataforma (String!)
Sección titulada «Detrás de cámaras: Tipos de Plataforma (String!)»Hay un secreto oscuro en el que Kotlin no te puede proteger mágicamente. Dado que Kotlin corre sobre la JVM, es altamente interoperable con Java. Puedes llamar a librerías escritas en Java directamente desde Kotlin.
El problema radica en que Java no tiene null safety estricto y la nulabilidad de sus retornos no siempre se conoce en tiempo de compilación. Cuando Kotlin recibe un objeto desde Java, a menudo no sabe si es seguro (String) o si podría ser nulo (String?).
Para resolver esto sin volver locos a los desarrolladores llenando el código de errores, Kotlin introduce los Platform Types (representados visualmente en el IDE con un ! al final, como String!).
Laboratorio Incremental: Capturando el Input
Sección titulada «Laboratorio Incremental: Capturando el Input»Hoy evolucionaremos nuestro Chat CLI. En lugar de solo imprimir un banner estático, le pediremos al usuario su nombre y lo saludaremos.
Pero, ¿qué pasa si el usuario presiona Ctrl+D (Fin del archivo) y no manda nada? La lectura será nula. Aquí es donde brillan nuestros conocimientos.
Entregables del Laboratorio
Sección titulada «Entregables del Laboratorio»Instrucciones Paso a Paso
Sección titulada «Instrucciones Paso a Paso»En la Lección 1 creamos nuestra función main y un pequeño banner. No borres ese código, ¡vamos a construir sobre él!
Paso 1: Pedir el nombre del usuario
Sección titulada «Paso 1: Pedir el nombre del usuario»Justo debajo de los println de tu banner de bienvenida, vamos a imprimir una instrucción para el usuario. Usa print (sin la terminación ln) para que el cursor se quede esperando en la misma línea:
print("Por favor, introduce tu nombre: ")Paso 2: Capturar el Input
Sección titulada «Paso 2: Capturar el Input»Ahora, vamos a leer lo que el usuario escriba. Para ello usamos la función readlnOrNull().
Agrega esta línea debajo de tu print:
// readlnOrNull() devuelve un String nullable (String?) porque la lectura// de la terminal podría fallar o interrumpirse inesperadamente.val input: String? = readlnOrNull()Paso 3: Validar la nulabilidad de forma segura
Sección titulada «Paso 3: Validar la nulabilidad de forma segura»Como input es de tipo String?, Kotlin no nos dejará usarlo ciegamente. Vamos a usar un bloque if/else clásico para validar matemáticamente si el usuario ingresó algo o si nos devolvió un nulo.
Agrega la siguiente lógica:
// Declaramos 'username' como val (Solo Lectura) porque no cambiará en este flujo.val username: String
// Validamos que input no sea nulo Y que no esté compuesto solo de espacios en blancoif (input != null && input.isNotBlank()) { username = input // ¡Smart Cast en acción! Aquí 'input' ya es un String seguro} else { username = "Invitado" // Nuestro valor por defecto}Paso 4: Saludar al usuario
Sección titulada «Paso 4: Saludar al usuario»Finalmente, agreguemos un saludo personalizado utilizando String Templates (inyectando la variable con $).
println("\n¡Hola, $username! ¿En qué te puedo ayudar hoy?")Paso 5: ¡Ejecuta y prueba en tu entorno favorito!
Sección titulada «Paso 5: ¡Ejecuta y prueba en tu entorno favorito!»Puedes ejecutar tu proyecto según la herramienta que estés utilizando:
-
Opción A (Android Studio / IntelliJ IDEA):
- Haz clic en el botón verde de Play (▶️) situado al lado izquierdo de la línea
fun main(). - En la pestaña inferior Run, escribe tu nombre cuando aparezca la pregunta y presiona Enter.
- Haz clic en el botón verde de Play (▶️) situado al lado izquierdo de la línea
-
Opción B (Google Antigravity / Visual Studio Code):
- Presiona el atajo
Cmd + Shift + B(en Mac) oCtrl + Shift + B(en Windows/Linux). También puedes abrir la paleta de comandos (Cmd + Shift + PoCtrl + Shift + P), escribirTasks: Run Tasky seleccionar Run AI Chat CLI. - La terminal integrada abrirá la sesión interactiva lista para que escribas tu nombre.
- Presiona el atajo
-
Opción C (Terminal de comandos):
- Abre tu terminal en la raíz del proyecto y ejecuta:
(En Windows CMD/PowerShell:
Ventana de terminal ./gradlew run --console=plain -qgradlew.bat run --console=plain -q) - Las banderas
--console=plain -qsuprimen las barras de estado del daemon de Gradle para que los prompts de texto y la interacción por teclado sean limpios.
- Abre tu terminal en la raíz del proyecto y ejecuta:
Comprobación de Null Safety:
Vuelve a ejecutar el programa, pero esta vez simplemente presiona Enter sin escribir nada (o introduce solo espacios). Comprobarás que tu lógica de validación te asigna el nombre "Invitado" de forma segura, evitando un temido NullPointerException.
¡Felicidades! Has interactuado exitosamente con la I/O (Input/Output) y has protegido tu aplicación de un colapso en tiempo de ejecución.
Fuentes y Lecturas Recomendadas
Sección titulada «Fuentes y Lecturas Recomendadas»Feedback y Dudas de la Lección
Sección titulada «Feedback y Dudas de la Lección»¿Tienes alguna duda sobre la mutabilidad (val/var), los Platform Types o el operador Elvis?