Tabla de contenido

Muy buenas a todos, Hoy vamos a continuar con la segunda parte de la lección de la excepción NullPointerException. Si tenéis interés en ver la primera parte de la lección y conocer algunos detalles más de esta excepción en particular y qué escenarios la causan, podéis leerla aquí.

La parte que nos toca hoy va más encaminada a cómo evitar este tipo de excepciones que a controlarlas. ¡Vamos a ello!
Recordando, las NullPointerException (NPE) son la mejor manera de resolver las referencias a objetos nulos, y además, son la clave para tener programas robustos y que se ejecutan de forma suave. Vamos a ver una serie de técnicas preventivas para evitar estas excepciones en gran medida. Lo que podemos resumir como: “Más vale prevenir, que curar…”.

Si seguimos estas técnicas, además minimizaremos el uso de las típicas comprobaciones !=null, lo que nos ahorrará mucho código. Probablemente, un programador experto conozca todas o algunas de estas técnicas, pero siempre vienen bien recordarlas o puede que sean útiles para programadores no tan duchos en la materia.

Consejos y mejores prácticas para evitar las NullPointerException

Enumeraremos una serie de reglas muy sencillas de seguir, pero que tendrán un impacto significante en nuestro código, añadiendo calidad y robustez.

Consejo 1
Invoca a los métodos equals() e equalsIgnoreCase() sobre objetos conocidos, en lugar de sobre objetos desconocidos.
La mejor idea es llamar siempre a equals() sobre objetos que tenemos total certeza que no son nulos. Por ejemplo sobre un String conocido, como veréis a continuación en el ejemplo.

De acuerdo, el método equals() es simétrico, es indiferente usar a.equals(b), que usar b.equals(a), pero elegir el orden apropiado puede ayudarnos en muchos momentos, y será de de las mejores prácticas que deberíamos empezar a aplicar. Siempre que no lo hagamos ya.

Este puede llegar a ser el consejo más sencillo y práctico que vais a encontrar hoy aquí, y si se usa, los resultados son tremendamente buenos, dado que el método equals() puede llegar a convertirse en algo muy usado en cualquier tipo de proyecto.

Consejo 2
Usar String.valueOf() en lugar de toString() dónde ambos nos den el mismo resultado.
Dado que el método toString() sobre objetos nulos nos lanza una NPE, si podemos obtener el mismo valor usando el método valueOf() entonces vamos a preferirlo de calle. Esto es debido a que si invocamos al método valueOf() sobre un objeto nulo, este nos devolverá “null”, de modo que es especialmente útil en clases “envueltas” (Wrapper clases), tales como Integer, Float, Double o BigDecimal.

Podemos usar este consejo si no estamos seguro de que los objetos usados sean nulos o no.

Consejo 3
Usar librerías y métodos ‘Null Safe’.
Puede que parezca una tontería, pero el uso de determinadas librerías en nuestro proyecto puede afectar en muchos puntos al desarrollo del mismo. Hay muchísimas librerías libres (Open source) por ahí esperando a ser usadas por ti, las cuales son capaces de realizar la ardua tarea de comprobar las referencias nulas, y ahorrarle el trabajo a tu persona. Una de las más famosas, y también puede que la más usada, es StringUtils de Apache Commons. Contiene una gran cantidad de métodos ‘Null Safe’ que cuando los descubrís pensáis: ¿Pero cómo no he visto yo esto antes…? Ahora en serio. Podemos usar distintos métodos muy útiles, tales como: StringUtils.isBlank(), StringUtils.isNumeric(), StringUtils.isWhiteSpace() y un largo etcétera, sin preocuparnos lo más mínimo de las NullPointerExceptions. He aquí algunos ejemplos esclarecedores:

Obviamente no debemos ser creyentes ciegos. Nuestra obligación es leer la documentación de las clases y métodos ‘Null Safe’ que usemos en nuestro código, de modo que no lleguemos a ninguna conclusión equivocada sobre los mismo. También podéis fiaros ciegamente de mí, es otra opción.

Por si os quedáis con ganas de más métodos ‘Null Safe’, aquí os doy otro muy útil. Se trata de ObjetUtils.equals() de Apache Commons. Viene muy al caso. Antes hemos hablado del método equals() y los casos en los que podía lanzar una NPE. Bueno, con este método todos esos problemas de orden y excepciones desaparecen. Cualquiera de los dos objetos puede ser nulo y simplemente obtendremos el booleano que corresponda a las entradas dadas.

Claro que no todo es idílico. Este método hace algunas comprobaciones más que equals(), lo que se traduce en más tiempo de ejecución, y puede llegar a afectar al rendimiento si estamos realizando una tarea que se basa en la velocidad de ejecución. Vale, Java tampoco es que sea el lenguaje más rápido del mundo, ni que vaya a consumir segundos realizando las comprobaciones pero tampoco es cuestión de matar moscas a cañonazos. Si sabemos que ciertos objetos nunca serán nulos, por ejemplo, ¿para qué hacer comprobaciones de más?

Este método, al menos en mi opinión, es algo a tener en cuenta para ciertas situaciones, y para nada es la mejor opción siempre. Por lo que a mí respecta no sustituye al método equals() de Object. Aquí algunos ejemplos para ver su salida para ciertas entradas:

Consejo 4
Evitar devolver NULL como salida de los métodos.
En lugar de devolver null, es mejor idea devolver una colección o array vacío. Devolviendo colecciones o arrays vacíos estamos seguros seguros de que llamadas como size(), length() no fallaran nunca con una NPE. De modo que evitamos esas comprobaciones inútiles antes de tratar una colección.

Collections.class dispone de constantes para listas, conjuntos y maps que pueden ser usados de la siguiente manera:

Consejo 5
Uso de las anotaciones @NotNull and @Nullable.
Mientras se desarrolla código se pueden añadir notas sobre la nulabilidad de cierto método dejando claro si este es ‘Null Safe’ o no lo es. Esto es posible gracias a las etiquetas @NotNull y @Nullable. Los compiladores, IDE o herramientas modernas pueden leer estas anotaciones y asistir a los usuarios sobre falta de comprobaciones en objetos potencialmente nulos, o sobre comprobaciones innecesarias que se han realizado.

Además, aunque no estemos trabajando con una herramienta capaz de llevar a cabo estas acciones, estas anotaciones funcionan como documentación, lo que significa que el programador echando un vistazo rápido a la documentación de cierto método puede decidir rápidamente si es necesaria una comprobación para que no sea nulo o no.

A pesar de todo, esto es una práctica algo nueva. No todos los programadores de Java la realizan a día de hoy y puede llevar algún tiempo en ser adoptada por completo. Igualmente, no es una mala práctica, y si estás aprendiendo en estos momentos (lo cual nunca se debería dejar de hacer) lo más recomendable es aplicar esta técnica.

Consejo 6
Evitar ‘autoboxing’ y ‘unboxing’ innecesarios en el código.
Y preguntareis, ¿qué es ‘autoboxing’ y ‘unboxing’?
Bueno, es normal que no lo sepais. Aunque lo usamos casi a diario en Java, pero de manera automática. Estas acciones son llevadas a cabo por el entorno de Java, de modo que no somos conscientes de ellas. Esto es un ejemplo de ‘autoboxing’:

Nosotros escribimos lo anterior, pero luego Java lo traducirá por algo así:

Y esto es un ejemplo de ‘unboxing’:

Lo que Java también traducirá por algo como:

Bueno, ahora que tenemos algo más claro lo que significan estos conceptos podemos proseguir con este consejo. La cosa es que a pesar de otras desventajas como la de crear objetos temporales, el ‘autoboxing’ y el ‘unboxing’ también son propensos a lanzar una NPE, en caso de que el objeto de la clase contenedora sea nulo.

Explico el ejemplo anterior: Tenemos el objeto Persona. Persona tiene diferentes atributos, tales como nombre, dirección y teléfono. Nombre y dirección son de tipo String, y teléfono es de tipo Integer. Usaremos un constructor que tiene como entrada el nombre, y no inicializaremos el resto de variables. Ahora pedimos el teléfono de ‘lazarus’, pero el valor de teléfono es nulo, luego al hacer el ‘unboxing’ obtendremos una NullPointerException. Que tonto, ¿verdad?

También existía la posibilidad de pedir el teléfono en una variable Integer, aquí no habríamos tenido problemas, y habríamos obtenido referencia a null en la variable destino.

Consejo 7
Seguir restricciones y definir valores por defecto razanobles.
Una de las mejores formas de evitar las NullPointerException en Java es tan simple como definir restricciones y seguirlas. La mayoria de las NPE suceden por crear objetos con información incompleta o porque todas las dependencias necesarias no han sido dadas. Si no permitimos crear objetos incompletos y de forma ‘bonita’ denegamos cualquier petición de creación, podemos prevenir muchas NPE por el camino.

De forma similar, si permitimos crear dichos objetos, deberíamos trabajar con valores por defecto razonables. Vamos a usar un ejemplo: El objeto Empleado no puede ser creado sin id ni nombre, pero el número de teléfono es opcional. Ahora bien, en lugar de devolver nulo cuando pedimos el número de teléfono de un Empleado que no lo tiene, devolvemos, por ejemplo, el valor cero.

Sin embargo, este tipo de decisiones debe ser tomada con mucho cuidado, dado que a veces comprobar si un valor es nulo o no puede ser más sencillo que comprobar si lo devuelto es un valor válido o no. Por cierto, una cosa más: definir qué puede ser nulo, y qué no ofrece al programador la posibilidad de tomar decisiones informadas en sus acciones en el momento que lo necesite, sin embargo, estas son importantes decisiones de diseño y han de ser tomadas y seguidas consecuentemente.

Consejo 8
Uso de restricciones en las bases de datos.
Si usamos bases de datos para guardar los objetos de nuestra aplicación, tales como Clientes, Pedidos, o cualquier otro objeto, deberíamos usar restricciones de nulabilidad en la misma base de datos.

Dado que las bases de datos pueden obtener datos de muy diversas fuentes, comprobar la nulabilidad justo antes de insertarlos garantizará la integridad de los datos. Además, las restricciones de la base de datos contribuirán a reducir las comprobaciones de nulabilidad en el código Java, teniendo la certeza de qué objetos pueden llegar a ser nulo y cuales no.

Consejo 9
Usar un objeto patrón Nulo.
Este es otro modo de evitar las NullPointerException en Java. Si un método devuelve un objeto, por ejemplo, un Iterador sobre cierta colección, pero en la llamada el objeto no dispone de un Iterador, devolvería null. En lugar de eso este método podría devolver el objeto de tipo Null.

¿Qué significa esto?, preguntareis. Un objeto de tipo Null es un objeto especial que tiene un significado diferente dependiendo del contexto donde se aplique. Por ejemplo, en este caso se puede tratar de un iterador vacío, y usando hasNext() sobre él devolverá siempre falso. Evitando de esa forma la NPE. De forma similar los métodos que devuelven colecciones, en lugar de null, podrían devolver colecciones vacías.

Para finalizar…

Bueno, esto es todo. Aquí damos por concluida la lección de ‘Cómo evitar las NullPointerException en Java’. Espero que algunos de estos consejos os sean útiles, y que apreciéis su importancia, dado que con muy poco esfuerzo inicial podemos llegar a ahorrar horas buscando errores en nuestro código. Además, no quiero irme sin hacer referencia al Blog Javarevisited cuya entrada, la cual podéis encontrar aquí, sirvió de base para esta lección sobre excepciones.

Espero que os sea de utilidad y hasta la próxima. A ser originales.
Lázarus Surazal.

Sé que esta entrada al Blog me quedó un poco más larga de lo normal, pero tampoco quería partir en dos esta parte de la lección. De este modo nos quedan dos partes, cada una con un contenido bien diferenciado de la otra. Además, si queréis contribuir con alguna idea más, la podéis escribir en los comentarios y la añadiré a la lista.

Entradas relacionadas

Perfil
prLázarus logo info
Carlos J. Peláez Rivas (Lázarus Surazal)
Graduado y Máster en Ingeniería Informática por la Universidad de Málaga. Actualmente trabajando como desarrollador de aplicaciones en Java usando Vaadin.
Apasionado de los videojuegos, la música y alguna que otra tecnología, siempre buscando cosas nuevas que aprender y hacer.
Más sobre mi...
Contacto
Notificaciones