Java opcional como tipo de retorno

Java opcional como tipo de retorno

1. Introdução

O tipoOptional foi introduzido no Java 8. Ele fornece uma maneira clara e explícita de transmitir a mensagem de que pode não haver um valor, sem usarnull.

Ao obter um tipo de retornoOptional, é provável que verifiquemos se o valor está faltando, levando a menosNullPointerExceptions nos aplicativos. No entanto, o tipoOptional não é adequado em todos os lugares.

Embora possamos usá-lo onde acharmos adequado, neste tutorial, vamos nos concentrar em algumas práticas recomendadas de uso deOptional como um tipo de retorno.

2. Optional como tipo de retorno

Um tipoOptional pode ser um tipo de retorno para a maioria dos métodos, exceto alguns cenários discutidos posteriormente no tutorial.

Na maioria das vezes, retornar umOptional é bom:

public static Optional findUserByName(String name) {
    User user = usersByName.get(name);
    Optional opt = Optional.ofNullable(user);
    return opt;
}

Isso é útil, pois podemos usar a APIOptional no método de chamada:

public static void changeUserName(String oldFirstName, String newFirstName) {
    findUserByFirstName(oldFirstName).ifPresent(user -> user.setFirstName(newFirstName));
}

Também é apropriado que um método estático ou método utilitário retorne um valorOptional. No entanto, existem muitas situações em que não devemos retornar o tipoan Optional.

3. Quando não retornarOptional

ComoOptional é um wrapper e uma classevalue-based, existem algumas operações que não podem ser feitas contra o objetoOptional. Muitas vezes, é simplesmente melhor retornar o tipo real em vez de um tipoOptional.

Em geral, para getters em POJOs, é mais adequado retornar o tipo real, não um tipoOptional. Particularmente, é importante que Beans de entidade, modelos de dados e DTOs tenham getters tradicionais.

Examinaremos alguns dos casos de uso importantes abaixo.

3.1. Serialização

Vamos imaginar que temos uma entidade simples:

public class Sock implements Serializable {
    Integer size;
    Optional pair;

    // ... getters and setters
}

This actually won’t work at all. Se tentássemos serializar isso, obteríamos umNotSerializableException:

new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(new Sock());

E realmente, enquantoserializing Optional may work with other libraries,it certainly adds what may be unnecessary complexity.

Vamos dar uma olhada em outro aplicativo dessa mesma incompatibilidade de serialização, desta vez com JSON.

3.2. JSON

Aplicativos modernos convertem objetos Java em JSON o tempo todo. Se um getter retornar um tipoOptional, provavelmente veremos alguma estrutura de dados inesperada no JSON final.

Digamos que temos um bean com uma propriedade opcional:

private String firstName;

public Optional getFirstName() {
    return Optional.ofNullable(firstName);
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

Portanto, se usarmos Jackson para serializar uma instância deOptional, obteremos:

{"firstName":{"present":true}}

Mas, o que realmente queremos é:

{"firstName":"example"}

Portanto,Optional  é uma dor para casos de uso de serialização. A seguir, vamos dar uma olhada no primo da serialização:writing data to a database.

3.3. JPA

Na JPA, o getter, o setter e o campo devem ter nome e tipo de contrato. Por exemplo, um campofirstName  do tipoString  deve ser emparelhado com um getter chamadogetFirstName que também retorna umString.

Seguir esta convenção simplifica várias coisas, incluindo o uso de reflexão por bibliotecas como o Hibernate, para nos dar um excelente suporte ao mapeamento de objeto-relacional.

Vamos dar uma olhada em nosso mesmo caso de uso dean optional first name in a POJO.

Desta vez, porém, será uma entidade JPA:

@Entity
public class UserOptionalField implements Serializable {
    @Id
    private long userId;

    private Optional firstName;

    // ... getters and setters
}

E vamos tentar persistir:

UserOptionalField user = new UserOptionalField();
user.setUserId(1l);
user.setFirstName(Optional.of("example"));
entityManager.persist(user);

Infelizmente, encontramos um erro:

Caused by: javax.persistence.PersistenceException: [PersistenceUnit: com.example.optionalReturnType] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1015)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:941)
    at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
    at com.example.optionalReturnType.PersistOptionalTypeExample.(PersistOptionalTypeExample.java:11)
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: UserOptionalField, for columns: [org.hibernate.mapping.Column(firstName)]

Poderíamos tentardeviating from this standard. . Por exemplo, poderíamos manter a propriedade comoString, mas alterar o getter:

@Column(nullable = true)
private String firstName;

public Optional getFirstName() {
    return Optional.ofNullable(firstName);
}

Parece que poderíamos ter as duas maneiras: ter um tipo de retornoOptional para o getter e um campo persistívelfirstName.

No entanto, agora que somos inconsistentes com nosso getter, setter e campo, será mais difícil aproveitar os padrões JPA e as ferramentas de código-fonte IDE.

Até que JPA tenha um suporte elegante do tipoOptional, devemos nos ater ao código tradicional. É mais simples e melhor:

private String firstName;

// ... traditional getter and setter

Vamos finalmente dar uma olhada em como isso afeta o front end - verifique se o problema que encontramos soa familiar.

3.4. Idiomas de expressão

A preparação de um DTO para o front-end apresenta dificuldades semelhantes.

Por exemplo, vamos imaginar que estamos usando modelos JSP para ler nossoUserOptional DTOfirstName a partir da solicitação:

Como é umOptional, não veremos “example“. Em vez disso, veremos a representaçãoString do tipoOptional:

Optional[example]

E isso não é um problema apenas com JSP. Qualquer linguagem de modelo, seja Velocity, Freemarker ou qualquer outra coisa, precisará adicionar suporte para isso. Até então, vamos continuar a manter nossos DTOs simples.

4. Conclusão

Neste tutorial, aprendemos como podemos retornar um objetoOptional e como lidar com esse tipo de valor de retorno.

Por outro lado, também aprendemos que há muitos cenários em que seria melhor não usar o tipo de retornoOptional para um getter. Embora possamos usar o tipoOptional como uma dica de que pode não haver nenhum valor não nulo, devemos ter cuidado para não usar excessivamente o tipo de retornoOptional, particularmente em um getter de um bean de entidade ou um DTO .

O código-fonte dos exemplos neste tutorial pode ser encontrado emGitHub.