Hablar de OPCIONAL en Java

 Una característica interesante introducida desde Java 8 es la clase Opcional. El principal problema que resuelve la clase Optional es la notoria NullPointerException, una excepción que todos los programadores de Java conocen muy bien.
 Esencialmente, esta es una clase contenedora que contiene valores opcionales, lo que significa que la clase Opcional puede contener objetos o estar vacía.

 Opcional es un paso poderoso hacia la programación funcional en Java y ayuda a lograrlo en un paradigma. Pero el significado de Opcional es obviamente más que eso.


Comenzamos con un caso de uso simple. Antes de Java 8, cualquier llamada para acceder a los métodos o propiedades del objeto puede causar NullPointerException:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

En este pequeño ejemplo, si necesitamos asegurarnos de que no se active ninguna excepción, tenemos que verificar explícitamente cada valor antes de acceder a él:

if (user != null) {
    
    
    Address address = user.getAddress();
    if (address != null) {
    
    
        Country country = address.getCountry();
        if (country != null) {
    
    
            String isocode = country.getIsocode();
            if (isocode != null) {
    
    
                isocode = isocode.toUpperCase();
            }
        }
    }
}

Como puede ver, esto puede volverse fácilmente detallado y difícil de mantener.

Para simplificar este proceso, echemos un vistazo a cómo hacerlo con la clase Opcional. Desde la creación y validación de una instancia, hasta el uso de sus diferentes métodos y la combinación con otros métodos que devuelven el mismo tipo, el siguiente es el momento de presenciar el milagro de Optional.


Crear instancia opcional

Para reiterar, los objetos de este tipo pueden contener valores o pueden estar vacíos. Puede crear un Opcional vacío utilizando el método del mismo nombre.

@Test(expected = NoSuchElementException.class)
public void whenCreateEmptyOptional_thenNull() {
    
    
    Optional<User> emptyOpt = Optional.empty();
    emptyOpt.get();
}

No es de extrañar que intentar acceder al valor de la variable emptyOpt resulte en una NoSuchElementException.

Puede usar los métodos of () y ofNullable () para crear un Opcional que contenga un valor. La diferencia entre los dos métodos es que si pasa un valor nulo como parámetro, el método of () arrojará una NullPointerException:

@Test(expected = NullPointerException.class)
public void whenCreateOfEmptyOptional_thenNullPointerException() {
    
    
    Optional<User> opt = Optional.of(user);
}

Verá, no estamos completamente libres de NullPointerException. Por lo tanto, debe usar of () cuando el objeto no es nulo.

Si el objeto es nulo o no nulo, debe usar el método ofNullable ():

Optional<User> opt = Optional.ofNullable(user);

Accede al valor del objeto opcional

Una de las formas de recuperar el objeto de valor real de la instancia opcional es usar el método get ():

@Test
public void whenCreateOfNullableOptional_thenOk() {
    
    
    String name = "John";
    Optional<String> opt = Optional.ofNullable(name);

    assertEquals("John", opt.get());
}

Sin embargo, como puede ver, este método generará una excepción cuando el valor sea nulo. Para evitar excepciones, puede optar por verificar que haya valores primero:

@Test
public void whenCheckIfPresent_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    Optional<User> opt = Optional.ofNullable(user);
    assertTrue(opt.isPresent());

    assertEquals(user.getEmail(), opt.get().getEmail());
}

Otra opción para comprobar si hay un valor es el método ifPresent (). Además de realizar la verificación, este método también acepta un parámetro Consumer (consumidor). Si el objeto no está vacío, ejecuta la expresión Lambda pasada:

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

En este ejemplo, la aserción se ejecutará solo cuando el usuario usuario no sea nulo.

A continuación, echemos un vistazo al método para proporcionar valores nulos.


Volver al valor predeterminado

La clase opcional proporciona una API para devolver el valor del objeto o devolver el valor predeterminado cuando el objeto está vacío.

El primer método que puede usar aquí es orElse (). Funciona de manera muy sencilla. Si hay un valor, devuelve ese valor; de lo contrario, devuelve el valor del parámetro que se le pasó:

@Test
public void whenEmptyValue_thenReturnDefault() {
    
    
    User user = null;
    User user2 = new User("[email protected]", "1234");
    User result = Optional.ofNullable(user).orElse(user2);

    assertEquals(user2.getEmail(), result.getEmail());
}

Aquí el objeto de usuario está vacío, por lo que se devuelve user2 como valor predeterminado.

Si el valor inicial del objeto no es nulo, se ignorará el valor predeterminado:

@Test
public void whenValueNotNull_thenIgnoreDefault() {
    
    
    User user = new User("[email protected]","1234");
    User user2 = new User("[email protected]", "1234");
    User result = Optional.ofNullable(user).orElse(user2);

    assertEquals("[email protected]", result.getEmail());
}

La segunda API del mismo tipo es orElseGet (); su comportamiento es ligeramente diferente. Este método devolverá un valor cuando haya un valor. Si no hay ningún valor, ejecutará la interfaz funcional del Proveedor pasada como parámetro y devolverá su resultado de ejecución:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

La diferencia entre orElse () y orElseGet ()

A primera vista, estos dos métodos parecen tener el mismo efecto. Sin embargo, no lo es. Creamos algunos ejemplos para resaltar las similitudes y diferencias en su comportamiento.

Echemos un vistazo a su comportamiento cuando el objeto está vacío:

@Test
public void givenEmptyValue_whenCompare_thenOk() {
    
    
    User user = null
    logger.debug("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.debug("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

private User createNewUser() {
    
    
    logger.debug("Creating New User");
    return new User("[email protected]", "1234");
}

En el código anterior, ambos métodos llaman al método createNewUser (), que registra un mensaje y devuelve el objeto Usuario.

La salida del código es la siguiente:

Using orElse
Creating New User
Using orElseGet
Creating New User

Esto muestra que cuando el objeto está vacío y se devuelve el objeto predeterminado, no hay diferencia en el comportamiento.


Veamos un ejemplo similar a continuación, pero aquí Opcional no está vacío:
@Test
public void givenPresentValue_whenCompare_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

La salida esta vez:

Using orElse
Creating New User
Using orElseGet

En este ejemplo, ambos objetos opcionales contienen valores no nulos y ambos métodos devolverán los valores no nulos correspondientes. Sin embargo, el método orElse () aún crea el objeto Usuario. Por el contrario, el método orElseGet () no crea un objeto Usuario.

Al realizar llamadas más intensivas, como llamadas a servicios web o consultas de datos, esta diferencia puede tener un impacto significativo en el rendimiento.


Devolver excepción

Además de los métodos orElse () y orElseGet (), Opcional también define la API orElseThrow (); generará una excepción cuando el objeto esté vacío, en lugar de devolver un valor alternativo:

@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
    
    
    User result = Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
}

Aquí, si el valor de usuario es nulo, se lanzará una IllegalArgumentException.

Este método nos permite tener una semántica más rica y podemos decidir qué tipo de excepción lanzar en lugar de lanzar siempre NullPointerException.

Ahora que tenemos una buena comprensión de cómo usar Opcional, echemos un vistazo a otros métodos que pueden transformar y filtrar valores Opcionales.


Valor de conversión

Hay muchas formas de convertir el valor de Opcional. Comenzamos con los métodos map () y flatMap ().

Veamos primero un ejemplo del uso de la API map ():

@Test
public void whenMap_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("[email protected]");

    assertEquals(email, user.getEmail());
}

map () aplica (llama) una función como parámetro al valor y luego envuelve el valor devuelto en un Opcional. Esto hace posible realizar llamadas de prueba en cadena en el valor de retorno; el siguiente ciclo aquí es orElse ().

Por el contrario, flatMap () también requiere una función como parámetro, llama a esta función en el valor y devuelve el resultado directamente.

En la siguiente operación, agregamos un método a la clase Usuario para devolver Opcional:

public class User {
    
        
    private String position;

    public Optional<String> getPosition() {
    
    
        return Optional.ofNullable(position);
    }

    //...
}

Dado que el método getter devuelve el Opcional del valor String, puede usarlo como un parámetro al llamar a flatMap () en el objeto Opcional del Usuario. El valor devuelto es el valor String descomprimido:

@Test
public void whenFlatMap_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");

    assertEquals(position, user.getPosition().get());
}

Valor de filtro

Además de convertir valores, la clase Opcional también proporciona métodos para "filtrar" valores según las condiciones.

filter () acepta un parámetro Predicate y devuelve el valor del resultado de la prueba como verdadero. Si el resultado de la prueba es falso, se devolverá un Opcional vacío.

Veamos un ejemplo de cómo decidir si aceptar o rechazar al usuario según la verificación básica del correo electrónico:

@Test
public void whenFilter_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

    assertTrue(result.isPresent());
}

Si se pasa la prueba del filtro, el objeto de resultado contendrá un valor no nulo.


Método de cadena de clase opcional

Para utilizar Optional de forma más completa, puede encadenar y combinar la mayoría de sus métodos, porque todos devuelven los mismos objetos y objetos similares.

Usamos Opcional para reescribir el primer ejemplo presentado.

Primero, refactorice la clase para que su método getter devuelva una referencia opcional:

public class User {
    
    
    private Address address;

    public Optional<Address> getAddress() {
    
    
        return Optional.ofNullable(address);
    }

    // ...
}
public class Address {
    
    
    private Country country;

    public Optional<Country> getCountry() {
    
    
        return Optional.ofNullable(country);
    }

    // ...
}

La estructura anidada anterior se puede representar con la siguiente figura:
Inserte la descripción de la imagen aquí
Ahora puede eliminar la verificación nula y reemplazarla con el método opcional:

@Test
public void whenChaining_thenOk() {
    
    
    User user = new User("[email protected]", "1234");

    String result = Optional.ofNullable(user)
      .flatMap(u -> u.getAddress())
      .flatMap(a -> a.getCountry())
      .map(c -> c.getIsocode())
      .orElse("default");

    assertEquals(result, "default");
}

El código anterior se puede reducir aún más mediante referencias de métodos:

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

Como resultado, el código ahora parece mucho más limpio que el código largo que antes usaba la ramificación condicional.


¿Cómo se debe utilizar Opcional?

Hay algunas cosas a considerar cuando se usa Opcional para decidir cuándo y cómo usarlo.

El punto importante es que Opcional no es serializable. Por lo tanto, no debe usarse como un campo de la clase.

Si el objeto que necesita serializar contiene valores opcionales, la biblioteca de Jackson admite el tratamiento de opcionales como objetos normales. En otras palabras, Jackson tratará los objetos vacíos como nulos y los objetos valorados tratarán su valor como el valor del dominio correspondiente. Esta característica está en el proyecto jackson-modules-java8.


Tampoco es muy útil en otro caso, cuando su tipo se utiliza como método o parámetro de método de construcción. Hacerlo complicará el código y es completamente innecesario:
User user = new User("[email protected]", "1234", Optional.empty());

Es mucho más fácil usar métodos sobrecargados para manejar parámetros innecesarios.

Opcional se utiliza principalmente como tipo de retorno. Después de obtener una instancia de este tipo, si tiene un valor, puede obtener este valor; de lo contrario, puede realizar algunas acciones alternativas.

La clase Optional tiene un caso de uso muy útil, que consiste en combinarla con flujos u otros métodos que devuelven Optional para construir una API fluida.

Veamos un ejemplo, usando el método findFirst () de Stream para devolver un objeto opcional:

@Test
public void whenEmptyStream_thenReturnDefaultOptional() {
    
    
    List<User> users = new ArrayList<>();
    User user = users.stream().findFirst().orElse(new User("default", "1234"));

    assertEquals(user.getEmail(), "default");
}

para resumir

Opcional es una adición útil al lenguaje Java; tiene como objetivo reducir NullPointerExceptions en el código, aunque no puede eliminar completamente estas excepciones.

También está bien diseñado y, naturalmente, incorpora las funciones compatibles con la funcionalidad de Java 8.

En general, esta clase simple y poderosa ayuda a crear programas que son más simples, más legibles y tienen menos errores que sus contrapartes.

Fuente: https://www.oschina.net/translate/understanding-accepting-and-leveraging-optional-in?lang=chs&page=2#

Supongo que te gusta

Origin blog.csdn.net/woaichihanbao/article/details/108071437
Recomendado
Clasificación