El valor nulo
El valor nulo (null), Es un valor especial que se utiliza para indicar justamente que no hay valor. Por ejemplo, si queremos que una variable en un momento dado no almacene ningún valor, le damos el valor null que a pesar de ser un valor tiene el significado especial de “no valor”.
No pienses que el valor Null es similar a 0(cero) o blanco o una “cadena vacía”, es un valor muy especial y no se comporta como ninguno de esos valores.
Kotlin es null safety
Kotlin es Null Safety, es decir, que gestiona los nulos de forma segura, de modo que puedes garantizar facilmente que tu código no va a producir NullPointerException (NPE).
¿Qué es NullPointerException?
Por el momento, simplemente indicar que es un error que se puede generar al ejecutar un programa que trabaja con nulos. Intenta intuir que es NullPointerException con el siguiente ejemplo. La segunda instrucción por razones que entenderás más adelante genera NullPointerException y ahí se para en seco la ejecución del programa, ninguna de las instrucciones que siguen a la segunda instrucción se van a ejecutar jamas. ¡Pruébalo!
¿Son malas las NPE?
No necesariamente. Depende del contexto y estilo de progrmación. Cuando estudiemos el control de excepciones por un lado y programación funcional por otro, entenderemos mejor este problema. Por el momento simplemente indicar que al usar en Kotlin técnicas de programación funcional las NPE se convierten en un engorro y se tuvo esto en cuenta en el diseño de Kotlin.
Por defecto una variable no puede tomar el valor null
Esto por ejemplo no compila
val x: Int = nullMarcar el tipo con ? para permitir nulos
Si quieres que una variable acepte nulos, tienes que marcar el tipo con una ?
val x: Int? = nullAñadir un ? al tipo se le denomina definir un tipo como anulable y permite por tanto que el tipo admita en su rango de valores el valor null que por defecto ningún tipo admite.
Chequeo de nulos en tiempo de compilación
Si permitimos expresamente que una variable tome el valor null, el compilador nos puede obligar a comprobar el nulo antes de hacer algo con esa variable para asegurarse de que no se producirá un NullPointerException.
En el siguiente ejemplo simplemente el valor de x lo intento imprimir y no hay problemas de compilación
El operador !!
El método menos seguro para el tratamiento de nulos es simplemente indicar al compilador que evite chequeos en tiempo de compilación respecto a la posibilidad de que se produzca una nullpointerException. Esta opción tiene sentido si:
- estoy completamente seguro que mi variable nunca va a llegarle un valor null
- Me da igual si se produce una NullPointerException.
Para indicar al compilador que evite el chequeo usamos el operador !! llamado operador de aserción de no nulo, o sea, que aseguramos al compilador que no se va a producir una NullPointerException y que si se produce asumimos la responsabilidad. En el siguiente ejemplo al escrib ir x!! no se genera error de compilación pues inhibimos el chequeo de nulos. Pero, ya que x vale null se genera en tiempo de ejecución una NullPointerException
Observa en el siguiente ejemplo como efectivamente usando !! puedo ocurrir un nullPointerException indeseado que para la ejecución del programa. Recuerda que si usas !! la responsabilidad del control de nulos pasa a ser total para el programador que es el que tendrá que asegurarse de que un NullPointerException no va a parar su programa
A continuación se profundiza más en el tratamiento de nulos pero con lo visto hasta aquí es suficiente para programas sencillos en los que estamos dispuestos a deshabilitar la comprobación de nulos de kotlin.
if para hacer un tratamiento seguro de nulos
Ahora el compilador no genera error ya que detecta que el código escrito aunque x puede valor null, si esto ocurre, no se ejecuta x.toDouble() ya que lo hemos prevenido con un if y por tanto el código es seguro
acceso seguro con el operador ? (operador call safe) y expresión de acceso seguro
Recuerda que Kotlin, por razones que entenderas más adelante, hay un interés claro por evitar la nullPointerException. Anteriormente lo conseguimos con un simple if. Otra forma de evitarla es usar el operador ? de acceso seguro. Todavía no estudiamos programación orientada a objetos pero adelantamos sencillamente que una NPE ocurre cuando se invoca a una propiedad o función miembro sobre null. De momento, simplificando razonamiento y casos posibles, podemos indicar que se produce una NPE cuando en una expresión de la forma variable.funcion o variable.propiedad si variable es null se produce NPE. Si añadimos una ? despues del nombre de una variable que puede valor null de la forma variable?.funcion o variable?.propiedad convertimos la expresión en “segura” en el sentido que no va a producir una NPE. Comprueba como compila
- Convertir un tipo en anulable. Esto ya lo analizamos más arriba
- despues de una variable en una expresión de la forma variable?.metodo. Si resulta que que la variable puede tomar el valor null, ocurre por tanto que la expresión en principio generaría una nullPointerException, pero el efecto de la interrogación es evitar esta excepción y que el valor que devuelva la expresión sea null
En el ejemmplo, como x es null y no especificamos el tipo de de y, el tipo se infiere de la expresión de la derecha y por tanto y se va a crear con tipo Double?, es decir admite valores Double pero también null
¿Cuál es entonces la diferencia entre x?.toDouble y x!!.toDouble()? Si pruebas los ejemplos anteriores observas que:
- x?.toDouble, si x vale nulo esta expresión devuelve null
- x!!.toDouble(), si xa vale null esta expresión genera una NPE.
Observa ahora el siguiente ejemplo
Da error de compilación ya que x?.toDouble() puede ser null y null + 2.0 no puede sumarse y da error. Para manejar situaciones como la anterior necesitamos un recurso adicional: el operador Elvis
El operador Elvis ?:
Se le llama operador elvis al operador ?: que nos permite completar la sintaxis de la expresión de acceso seguro para matizar, cuando nos interese, que en lugar de devolver nulo devuelva un valor concreto, por ejemplo, ahora si x vale null entonces a y le asignamos 0.0
var y = x?.toDouble()?:0.0no es más que la forma abreviada de
var y = if (x!=null)x.toDouble() else 0.0¡compruébalo!
y volviendo a nuetro problema planteado al estudiar la sección anterior, lo solucionamos a continuación con el operador Elvis
