Ejemplos de Uso
Ahora que hemos aprendido por qué son usados los Optional
s, echemos un vistazo a lo que realmente podemos hacer con ellos en Java. Estos ejemplos de código (y Sponge) usan la clase `java.util.Optional<https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html>`_ disponible en Java 8.
Obteniendo el Valor envuelto
El método get()
desplegará un `` Optional`` y devolverá el valor envuelto. Si no hay ningún valor presente, al llamar a get()
arrojará una NoSuchElementException
, por lo que primero se debe realizar una comprobación de presencia.
Optional<String> opt = getOptionalString();
String wrappedString = opt.get();
Manejo de Valores ausentes
El propósito del tipo Optional
es representar un valor que podría o no podría estar allí. Como tal, muchos casos de uso giran en torno al manejo de valores ausentes.
Verificación de presencia
El método isPresent()
devuelve verdadero si hay un valor presente en Optional
. Puede proporcionar la verificación más básica y es equivalente a una comprobación clásica de null
.
Optional<String> opt = getOptionalString();
if (opt.isPresent()) {
String wrappedString = opt.get();
// more code
}
Usar Valores predeterminados
Un patrón común es volver a un valor predeterminado si no hay ninguno presente. El método orElse()
permite una declaración de línea única que devolverá el valor presente en Optional
o el valor predeterminado proporcionado.
En lugar de
Optional<String> optionalString = getOptionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = DEFAULT_STRING;
}
solo use
String someString = getOptionalString().orElse(DEFAULT_STRING);
In some cases, a default value has to be calculated in a way that has side effects or is particularly expensive. In such
a case it is desirable to calculate the default value only if needed (lazy evaluation). The orElseGet()
method
accepts a Supplier
instead of a pre-calculated value. If no value is present on the Optional
itself, the
Supplier
will be called. Since Supplier
is a functional interface, a lambda expression or method reference can
be passed instead.
En lugar de
Optional<String> optionalString = getOptionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = myPlugin.defaultString();
}
solo use
String someString = getOptionalString().orElseGet(myPlugin::defaultString);
Falla en Valores ausentes
Si un valor que está ausente debe guiar a una excepción, casi siempre es mejor lanzar una excepción personalizada en lugar de confiar en la NoSuchElementException
predeterminada. Si Ud. llama al método orElseThrow()
con un Supplier
, devolverá el valor envuelto si está presente, o arrojará un Throwable
obtenido del Supplier
si Optional
está vacío. Nuevamente, como Supplier
es una interfaz funcional, en su lugar se pueden usar expresiones lambda o referencias de métodos.
En lugar de
Optional<String> optionalString = getOptionalString();
if (!optionalString.isPresent()) {
throw new MyException();
}
String someString = optionalString.get();
solo use
String someString = getOptionalString().orElseThrow(MyException::new);
Nota
Si el Throwable
provisto por el proveedor es una excepción marcada, también deberá incluirse en la firma de la función circundante (por ejemplo, public void doStuff() throws MyException
)
Ejecución de Código Condicional
Si no se puede usar ningún valor predeterminado, el código que se basa en un valor presente no puede ser ejecutado. Si bien esto podría tratarse en una condición simple, existen otros métodos convenientes.
Valores de Consumo
Si su lógica para manejar el valor actual ya está encapsulada en un Consumer
o una función de parámetro único, el método ifPresent()
aceptará al consumidor (o una referencia de método). Si hay un valor presente en el Optional
, se pasará al consumidor. Si el Optional
está vacío, nada pasará.
En lugar de
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
myPlugin.doSomethingWithString(optionalString.get());
}
solo use
Optional<String> optionalString = getOptionalString();
optionalString.ifPresent(s -> myPlugin.doSomethingWithString(s));
o
getOptionalString().ifPresent(myPlugin::doSomethingWithString);
Filtración
También es posible pasar un Predicate
. Solo se conservarán los valores que este Predicate
devuelva verdadero. Si no hay ningún valor presente o Predicate
devuelve false
, se devolverá un Optional
vacío. Desde que este método devuelve una opción, permite el encadenamiento con otros métodos.
En lugar de
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
String someString = optionalString.get();
if (stringTester.isPalindromic(someString)) {
myPlugin.doSomethingWithString(someString);
}
}
solo use
getOptionalString()
.filter(stringTester::isPalindromic)
.ifPresent(myPlugin::doSomethingWithString);
Nota
Ni esta función de filtrado ni las funciones de mapeo que se describen a continuación modifican la instancia en la que son llamadas. Optional
s son siempre inmutables.
Mapeo
Otra operación encadenable es mapear el valor potencial a uno diferente. Si no hay ningún valor presente, nada cambiará. Pero si está presente, el método map()
devolverá un Optional
del valor devuelto por la Function
proporcionada (o un Optional
vacío si ese valor de retorno es null
).
En lugar de
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
String someString = optionalString.get().toLowerCase();
myPlugin.doSomethingWithString(someString);
}
solo use
getOptionalString()
.map(s -> s.toLowerCase())
.ifPresent(myPlugin::doSomethingWithString);
Truco
Si su función de mapeo ya devuelve un Optional
, use el método flatMap()
en su lugar. Este se comportará como map()
, excepto que este espera que la función de mapeo ya devuelva Optional
y por lo tanto no envolverá el resultado.
Ejemplo Combinado
Imagine un plugin que le permita a cada jugador tener un seguimiento de mascota. Asuma la existencia de los siguientes métodos:
petRegistry.getPetForPlayer()
acepta unPlayer
y devuelveOptional<Pet>
. Este método busca la mascota asociada con un jugador dadopetHelper.canSpawn()
aceptandoPet
y devolviendoboolean
. Este método realiza todas las comprobaciones necesarias para asegurarse de que la mascota determinada se genere.petHelper.spawnPet()
acepta unPet
y no devuelve nada. Este método generará una mascota no engendrada previamente.
Ahora desde algún lugar (probablemente la ejecución de un comando) tenemos la variable optionalPlayer
que contiene un Optional<Player>
. Ahora queremos obtener esta mascota de estos jugadores, verificar si la mascota se genera y si no se genera, engendrarla mientras se realizan las verificaciones correspondientes si todos y cada uno de los Optional
realmente contienen un valor. El código que usa solo los métodos básicos isPresent()
y get()
se pone desagradable muy rápido.
if (optionalPlayer.isPresent()) {
Player player = optionalPlayer.get();
Optional<Pet> optionalPet = petRegistry.getPetForPlayer(player);
if (optionalPet.isPresent()) {
Pet pet = optionalPet.get();
if (petHelper.canSpawn(pet)) {
petHelper.spawnPet(pet);
}
}
}
However, through the use of Optional
methods for conditional code execution, all those presence checks are hidden,
reducing the boilerplate and indentation levels and thus leaving the code much more readable:
optionalPlayer
.flatMap(petRegistry::getPetForPlayer)
.filter(petHelper::canSpawn)
.ifPresent(petHelper::spawnPet);
Creando Opcionales
Si elige proporcionar una API siguiendo el mismo contrato de usar Optional
en lugar de devolver valores null
, Ud. tendrá que crear Optional
s para poder devolverlos. Esto se hace llamando a uno de los tres métodos de construcción estáticos.
Optional.empty()
siempre devolverá un Optional
vacío.
Optional.of()
devolverá un ajuste opcional del valor dado y generará una NullPointerException
si el valor fuera null
.
Optional.ofNullable()
devolverá un Optional
vacío si el valor proporcionado es null
, de lo contrario devolverá un Optional
que envuelve el valor.