Ejemplos de Uso
Ahora que hemos aprendido por qué son usados los Optionals, 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 = optionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = DEFAULT_STRING;
}
solo use
String someString = getOptionalString().orElse(DEFAULT_STRING);
En algunos casos, un valor predeterminado debe calcularse de manera tal que tenga efectos secundarios o si es particularmente costoso. En tal caso, es deseable calcular el valor predeterminado solo si es necesario (evaluación diferida). El método orElseGet() acepta un Supplier en lugar de un valor precalculado. Si no hay ningún valor presente en el Optional en sí, se llamará al Supplier. Como Supplier es una interfaz funcional, en su lugar se puede pasar una expresión lambda o una referencia de método.
En lugar de
Optional<String> optionalString = optionalString();
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 = optionalString();
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. Optionals 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.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 unPlayery devuelveOptional<Pet>. Este método busca la mascota asociada con un jugador dadopetHelper.canSpawn()aceptandoPety devolviendoboolean. Este método realiza todas las comprobaciones necesarias para asegurarse de que la mascota determinada se genere.petHelper.spawnPet()acepta unPety 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);
}
}
}
Sin embargo, mediante el uso de los métodos Optionals para la ejecución de código condicional, todas esas comprobaciones de presencia están ocultas, lo que reduce los niveles repetitivos e indentados y así deja el código mucho mas legible:
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 Optionals 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.