Fale sobre OPCIONAL em Java

 Um recurso interessante introduzido a partir do Java 8 é a classe opcional. O principal problema que a classe Optional resolve é a notória NullPointerException - uma exceção que todo programador Java conhece muito bem.
 Essencialmente, esta é uma classe de invólucro que contém valores opcionais, o que significa que a classe Opcional pode conter objetos ou estar vazia.

 Opcional é uma etapa poderosa em direção à programação funcional em Java e ajuda a alcançá-la em um paradigma. Mas o significado de Opcional é obviamente mais do que isso.


Começamos com um caso de uso simples. Antes do Java 8, qualquer chamada para acessar métodos ou propriedades de objetos pode causar NullPointerException:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

Neste pequeno exemplo, se precisarmos garantir que nenhuma exceção seja disparada, temos que verificar explicitamente cada valor antes de acessá-lo:

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 você pode ver, isso pode facilmente se tornar prolixo e difícil de manter.

Para simplificar esse processo, vamos dar uma olhada em como fazê-lo com a classe Opcional. Desde a criação e validação de uma instância até o uso de seus diferentes métodos e a combinação com outros métodos que retornam o mesmo tipo, o seguinte é o momento de testemunhar o milagre do Optional.


Criar instância opcional

Para reiterar, os objetos deste tipo podem conter valores ou podem estar vazios. Você pode criar um opcional vazio usando o método de mesmo nome.

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

Não é surpresa que tentar acessar o valor da variável emptyOpt resultará em uma NoSuchElementException.

Você pode usar os métodos of () e ofNullable () para criar um Optional que contém um valor. A diferença entre os dois métodos é que se você passar um valor nulo como parâmetro, o método of () lançará uma NullPointerException:

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

Veja, não estamos completamente livres de NullPointerException. Portanto, você deve usar of () quando o objeto não for nulo.

Se o objeto for nulo ou não nulo, você deve usar o método ofNullable ():

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

Acesse o valor do objeto opcional

Uma das maneiras de recuperar o objeto de valor real da instância opcional é usar o método get ():

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

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

No entanto, como você pode ver, esse método lançará uma exceção quando o valor for nulo. Para evitar exceções, você pode optar por verificar se há valores primeiro:

@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());
}

Outra opção para verificar se existe um valor é o método ifPresent (). Além de realizar a verificação, este método também aceita um parâmetro Consumidor (consumidor). Se o objeto não estiver vazio, ele executa a expressão Lambda passada:

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

Neste exemplo, a asserção será executada apenas quando o usuário usuário não for nulo.

A seguir, vamos dar uma olhada no método de fornecimento de valores nulos.


Retornar ao padrão

A classe opcional fornece uma API para retornar o valor do objeto ou retornar o valor padrão quando o objeto está vazio.

O primeiro método que você pode usar aqui é orElse (). Funciona de forma muito simples. Se houver um valor, ele retorna esse valor, caso contrário, retorna o valor do parâmetro passado a ele:

@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());
}

Aqui, o objeto de usuário está vazio, portanto, user2 como o valor padrão é retornado.

Se o valor inicial do objeto não for nulo, o valor padrão será ignorado:

@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());
}

A segunda API do mesmo tipo é orElseGet () - seu comportamento é ligeiramente diferente. Este método retornará um valor quando houver um valor. Se não houver valor, ele executará a interface funcional do Fornecedor passada como parâmetro e retornará o resultado da execução:

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

A diferença entre orElse () e orElseGet ()

À primeira vista, esses dois métodos parecem ter o mesmo efeito. No entanto, não é. Criamos alguns exemplos para destacar as semelhanças e diferenças em seu comportamento.

Vamos dar uma olhada em seu comportamento quando o objeto está vazio:

@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");
}

No código acima, os dois métodos chamam o método createNewUser (), que registra uma mensagem e retorna o objeto User.

A saída do código é a seguinte:

Using orElse
Creating New User
Using orElseGet
Creating New User

Isso mostra que quando o objeto está vazio e o objeto padrão é retornado, não há diferença no comportamento.


Vejamos um exemplo semelhante a seguir, mas aqui Opcional não está vazio:
@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());
}

A saída desta vez:

Using orElse
Creating New User
Using orElseGet

Neste exemplo, ambos os objetos Opcionais contêm valores não nulos e ambos os métodos retornarão os valores não nulos correspondentes. No entanto, o método orElse () ainda cria o objeto User. Em contraste, o método orElseGet () não cria um objeto Usuário.

Ao realizar chamadas mais intensas, como chamadas de serviços da Web ou consultas de dados, essa diferença pode ter um impacto significativo no desempenho.


Exceção de retorno

Além dos métodos orElse () e orElseGet (), Optional também define a API orElseThrow () - lançará uma exceção quando o objeto estiver vazio, em vez de retornar um valor alternativo:

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

Aqui, se o valor do usuário for nulo, uma IllegalArgumentException será lançada.

Este método nos permite ter uma semântica mais rica e pode decidir que tipo de exceção lançar em vez de sempre lançar NullPointerException.

Agora que temos um bom entendimento de como usar Optional, vamos dar uma olhada em outros métodos que podem transformar e filtrar valores Opcionais.


Valor de conversão

Existem muitas maneiras de converter o valor de Opcional. Começamos com os métodos map () e flatMap ().

Vejamos primeiro um exemplo de uso da 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 (chama) uma função como um parâmetro para o valor e, em seguida, envolve o valor retornado em um opcional. Isso torna possível realizar chamadas de teste em cadeia no valor de retorno - o próximo loop aqui é orElse ().

Em contraste, flatMap () também requer uma função como parâmetro, chama essa função no valor e retorna o resultado diretamente.

Na operação a seguir, adicionamos um método à classe User para retornar Optional:

public class User {
    
        
    private String position;

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

    //...
}

Como o método getter retorna o valor Opcional do String, você pode usá-lo como um parâmetro ao chamar flatMap () no objeto Opcional do usuário. O valor retornado é o valor String descompactado:

@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 do filtro

Além de converter valores, a classe Opcional também fornece métodos para "filtrar" valores com base nas condições.

filter () aceita um parâmetro Predicate e retorna o valor do resultado do teste como verdadeiro. Se o resultado do teste for falso, um opcional vazio será retornado.

Vejamos um exemplo de decisão de aceitar ou rejeitar o usuário com base na verificação básica de e-mail:

@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());
}

Se o teste de filtro for aprovado, o objeto de resultado conterá um valor não nulo.


Método em cadeia da classe opcional

Para usar Optional mais completamente, você pode encadear e combinar a maioria de seus métodos, porque todos eles retornam os mesmos objetos semelhantes.

Usamos Opcional para reescrever o primeiro exemplo apresentado.

Primeiro, refatore a classe para que seu método getter retorne uma referência 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);
    }

    // ...
}

A estrutura aninhada acima pode ser representada pela seguinte figura:
Insira a descrição da imagem aqui
Agora você pode excluir a verificação de nulo e substituí-la pelo 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");
}

O código acima pode ser reduzido ainda mais por referências de método:

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

Como resultado, o código agora parece muito mais limpo do que o código extenso que usava a ramificação condicional antes.


Como o opcional deve ser usado?

Há algumas coisas a se considerar ao usar o opcional para decidir quando e como usá-lo.

O ponto importante é que Opcional não é serializável. Portanto, não deve ser usado como um campo da classe.

Se o objeto que você precisa serializar contém valores opcionais, a biblioteca Jackson suporta o tratamento de objetos opcionais como normais. Em outras palavras, Jackson tratará objetos vazios como nulos e objetos com valor tratarão seu valor como o valor do domínio correspondente. Este recurso está no projeto jackson-modules-java8.


Também não é muito útil em outro caso, quando seu tipo é usado como parâmetro de método ou método de construção. Fazer isso complicará o código e é completamente desnecessário:
User user = new User("[email protected]", "1234", Optional.empty());

É muito mais fácil usar métodos sobrecarregados para lidar com parâmetros desnecessários.

Opcional é usado principalmente como um tipo de retorno. Depois de obter uma instância desse tipo, se ela tiver um valor, você pode obter esse valor, caso contrário, pode executar algumas ações alternativas.

A classe Optional tem um caso de uso muito útil, que é combiná-la com fluxos ou outros métodos que retornam Optional para construir uma API fluente.

Vejamos um exemplo, usando o método findFirst () de Stream para retornar um 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");
}

Resumindo

Opcional é uma adição útil à linguagem Java - tem como objetivo reduzir NullPointerExceptions no código, embora não possa eliminar completamente essas exceções.

Ele também é bem projetado e incorpora naturalmente as funções suportadas pela funcionalidade Java 8.

Em geral, essa classe simples e poderosa ajuda a criar programas que são mais simples, mais legíveis e têm menos erros do que seus equivalentes.

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

Acho que você gosta

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