Utilizar Optional
para seguridad frente a valores nulos
En este paso, exploraremos un enfoque más moderno para manejar posibles valores null
en Java utilizando la clase Optional
, introducida en Java 8. Optional
es un objeto contenedor que puede o no contener un valor no nulo. Proporciona una forma de representar de manera más explícita la presencia o ausencia de un valor, lo que puede ayudar a reducir el riesgo de NullPointerException
.
Si bien las comprobaciones if (collection != null)
son perfectamente válidas y necesarias en muchas situaciones, Optional
puede hacer que tu código sea más legible y expresivo, especialmente cuando se trata de métodos que pueden devolver un valor o pueden devolver null
.
Veamos cómo podemos utilizar Optional
con una colección. Aunque Optional
se utiliza típicamente para valores individuales, es posible que encuentres escenarios en los que un método devuelva un Optional<List<SomeObject>>
.
-
Abre el archivo HelloJava.java
en el editor WebIDE.
-
Reemplaza el contenido con el siguiente código que demuestra el uso de Optional
con una lista potencialmente nula:
import java.util.List;
import java.util.ArrayList;
import java.util.Optional; // Import Optional
public class HelloJava {
// Un método que puede devolver un Optional que contiene una lista, o un Optional vacío
public static Optional<List<String>> getNames(boolean includeNames) {
if (includeNames) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
return Optional.of(names); // Devuelve un Optional que contiene la lista
} else {
return Optional.empty(); // Devuelve un Optional vacío
}
}
public static void main(String[] args) {
// Caso 1: Obtener nombres cuando includeNames es true
Optional<List<String>> namesOptional1 = getNames(true);
System.out.println("Comprobando namesOptional1:");
// Comprobar si el Optional contiene un valor
if (namesOptional1.isPresent()) {
List<String> names = namesOptional1.get(); // Obtener la lista del Optional
System.out.println("La lista está presente. Tamaño: " + names.size());
// También puedes comprobar si la lista en sí está vacía
if (names.isEmpty()) {
System.out.println("La lista está vacía.");
} else {
System.out.println("La lista no está vacía. Primer nombre: " + names.get(0));
}
} else {
System.out.println("La lista no está presente (Optional está vacío).");
}
System.out.println("---");
// Caso 2: Obtener nombres cuando includeNames es false
Optional<List<String>> namesOptional2 = getNames(false);
System.out.println("Comprobando namesOptional2:");
if (namesOptional2.isPresent()) {
List<String> names = namesOptional2.get();
System.out.println("La lista está presente. Tamaño: " + names.size());
if (names.isEmpty()) {
System.out.println("La lista está vacía.");
} else {
System.out.println("La lista no está vacía. Primer nombre: " + names.get(0));
}
} else {
System.out.println("La lista no está presente (Optional está vacío).");
}
System.out.println("\nPrograma finalizado.");
}
}
En este código:
- Definimos un método
getNames
que devuelve un Optional<List<String>>
. Este método simula un escenario en el que puedes obtener una lista o no obtener nada (representado por un Optional
vacío).
- En el método
main
, llamamos a getNames
con true
y false
para probar ambos casos.
- Usamos
namesOptional.isPresent()
para comprobar si el Optional
contiene una lista.
- Si
isPresent()
es verdadero, usamos namesOptional.get()
para recuperar la lista. Esto es seguro porque ya hemos comprobado la presencia.
- Dentro del bloque
isPresent()
, luego podemos realizar comprobaciones en la lista en sí, como names.isEmpty()
.
-
Guarda el archivo.
-
Compila el programa en la Terminal:
javac HelloJava.java
-
Ejecuta el programa:
java HelloJava
Deberías ver una salida similar a esta:
Comprobando namesOptional1:
La lista está presente. Tamaño: 2
La lista no está vacía. Primer nombre: Alice
---
Comprobando namesOptional2:
La lista no está presente (Optional está vacío).
Programa finalizado.
Esta salida muestra cómo Optional
nos ayuda a manejar el caso en el que una lista puede no ser devuelta en absoluto.
Optional
también proporciona otros métodos útiles para manejar la ausencia de un valor, como:
orElse(defaultValue)
: Devuelve el valor si está presente, de lo contrario devuelve un valor predeterminado.
orElseGet(supplier)
: Devuelve el valor si está presente, de lo contrario devuelve el resultado de la función supplier
.
orElseThrow(exceptionSupplier)
: Devuelve el valor si está presente, de lo contrario lanza una excepción producida por el exceptionSupplier
.
ifPresent(consumer)
: Realiza la acción dada si un valor está presente.
Modifiquemos el código para usar ifPresent
de una manera más concisa para manejar la lista cuando está presente.
-
Abre HelloJava.java
en el editor.
-
Modifica el método main
para usar ifPresent
:
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
public class HelloJava {
public static Optional<List<String>> getNames(boolean includeNames) {
if (includeNames) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
return Optional.of(names);
} else {
return Optional.empty();
}
}
public static void main(String[] args) {
// Caso 1: Obtener nombres cuando includeNames es true
Optional<List<String>> namesOptional1 = getNames(true);
System.out.println("Comprobando namesOptional1 usando ifPresent:");
namesOptional1.ifPresent(names -> {
// Este bloque solo se ejecuta si namesOptional1 contiene una lista
System.out.println("La lista está presente. Tamaño: " + names.size());
if (names.isEmpty()) {
System.out.println("La lista está vacía.");
} else {
System.out.println("La lista no está vacía. Primer nombre: " + names.get(0));
}
});
if (!namesOptional1.isPresent()) { // Todavía se necesita una comprobación si se debe manejar el caso de ausencia
System.out.println("La lista no está presente (Optional está vacío).");
}
System.out.println("---");
// Caso 2: Obtener nombres cuando includeNames es false
Optional<List<String>> namesOptional2 = getNames(false);
System.out.println("Comprobando namesOptional2 usando ifPresent:");
namesOptional2.ifPresent(names -> {
System.out.println("La lista está presente. Tamaño: " + names.size());
if (names.isEmpty()) {
System.out.println("La lista está vacía.");
} else {
System.out.println("La lista no está vacía. Primer nombre: " + names.get(0));
}
});
if (!namesOptional2.isPresent()) {
System.out.println("La lista no está presente (Optional está vacío).");
}
System.out.println("\nPrograma finalizado.");
}
}
Reemplazamos la estructura if (namesOptional.isPresent()) { ... namesOptional.get() ... }
con namesOptional.ifPresent(names -> { ... })
. El código dentro de la expresión lambda (names -> { ... }
) solo se ejecutará si el Optional
contiene un valor. Todavía agregamos una comprobación if (!namesOptional.isPresent())
para manejar el caso en el que el Optional
está vacío, ya que ifPresent
solo maneja el caso de presencia.
-
Guarda el archivo.
-
Compila el programa:
javac HelloJava.java
-
Ejecuta el programa:
java HelloJava
La salida debe ser la misma que antes, lo que demuestra que ifPresent
proporciona una forma alternativa de manejar la presencia de un valor en un Optional
.
Utilizar Optional
puede hacer que la intención de tu código sea más clara en cuanto a si un valor puede estar ausente, y te anima a manejar esa ausencia de manera explícita, reduciendo la probabilidad de NullPointerException
inesperados.