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 un Player y devuelve Optional<Pet>. Este método busca la mascota asociada con un jugador dado

  • petHelper.canSpawn() aceptando Pet y devolviendo boolean. Este método realiza todas las comprobaciones necesarias para asegurarse de que la mascota determinada se genere.

  • petHelper.spawnPet() acepta un Pet 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);
        }
    }
}

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.