Então, o que o VP do TPM fez de errado? Além disso, o que o revisor de código e o desenvolvedor fizeram de errado? Bem, as respostas estão no final deste artigo, mas se você não intuiu que o código que vai entrar em produção ainda está aberto para o NPE, então talvez este artigo seja para você. E, sim, de alguma forma, o código passou na fase de testes também.

A melhor maneira de usar as coisas é explorá-las para o que elas foram criadas e testadas em primeiro lugar. O Java 8 Optional não é uma exceção a essa regra. O objetivo do Java 8 Optional é claramente definido por Brian Goetz, arquiteto de linguagem do Java:

O opcional tem como objetivo fornecer um mecanismo limitado para os tipos de retorno do método de biblioteca, em que é necessário que haja uma maneira clara de representar “nenhum resultado”, e usar nulo para isso é extremamente provável de causar erros.

Então, como usar o Optional do jeito que foi planejado? Normalmente, aprendemos a usar as coisas aprendendo a não usá-las e, de alguma forma, essa é a abordagem aqui também. Então, vamos abordar este tópico através de 26 itens. Este é um conjunto de itens que tentam resolver o Optional em seu código por meio de uma abordagem elegante e indolor.

Item 1: Nunca Atribuir Nulo a uma Variável Opcional

Evitar:

// AVOID 
public Optional<Cart> fetchCart() {
    Optional<Cart> emptyCart = null;
    ...
} 

Prefira:

// PREFER
public Optional<Cart> fetchCart() {
    Optional<Cart> emptyCart = Optional.empty();
    ...
}

Prefira Optional.empty() para inicializar um valor Optional vez de um valor null. Optional é apenas um contêiner/caixa e é inútil inicializá-lo com null.

Item 2: Assegure-se de que um opcional tenha um valor antes de chamar Optional.get ()

Se, por qualquer razão, você decidir que o Optional.get() fará o seu dia, então não se esqueça de que você deve provar que o valor Optional está presente antes desta chamada. Normalmente, você fará isso adicionando uma verificação (condição) com base no método Optional.isPresent(). O isPresent()-get() tem uma má reputação (verifique itens adicionais para alternativas), mas se este é o caminho escolhido, então não se esqueça da parte isPresent(). No entanto, lembre-se de que Optional.get() é propenso a ser preterido em algum momento.

Evitar:

// AVOID
Optional<Cart> cart = ... ; // this is prone to be empty
...
// if "cart"is empty then this code will throw a java.util.NoSuchElementException
Cart myCart = cart.get();

Prefira:

// PREFER
if (cart.isPresent()) {
    Cart myCart = cart.get();
    ... // do something with "myCart"
} else {
    ... // do something that doesn't call cart.get()
}

Item 3: quando nenhum valor está presente, definir/retornar um objeto padrão já construído por meio do método Optional.orElse()

O uso do método Optional.orElse() representa uma alternativa elegante ao isPresent()-get() para definir/retornar um valor. O importante aqui é que o parâmetro orElse() é avaliado mesmo quando se tem um Optional não vazio. Isso significa que ele será avaliado mesmo que não seja usado, o que é uma penalidade de desempenho. Nesse contexto, use orElse() somente quando o parâmetro (o objeto padrão) já estiver construído. Em outras situações, confie no item 4.

Evitar:

// AVOID
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        return USER_STATUS;
    }
}

Prefira:

// PREFER
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    return status.orElse(USER_STATUS);
}

Item 4: quando nenhum valor está presente, definir/retornar um objeto padrão não existente por meio do método Optional.orElseGet()

Usar o método Optional.orElseGet() representa outra alternativa elegante ao isPresent()-get() para definir/retornar um valor. O importante aqui é que o parâmetro de orElseGet() é um Java 8, Supplier. Isso significa que o método Supplier passado como um argumento só é executado quando um valor Optional não está presente. Portanto, isso é útil para evitar a penalidade de desempenho orElse() da criação de objetos e a execução de código que não precisamos quando um valor Optional está presente.

Evitar:

// AVOID
public String computeStatus() {
    ... // some code used to compute status
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        return computeStatus();
    }
}

Além disso, evite:

// AVOID
public String computeStatus() {
    ... // some code used to compute status
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    // computeStatus() is called even if "status" is not empty
    return status.orElse(computeStatus()); 
}

Prefira:

// PREFER
public String computeStatus() {
    ... // some code used to compute status
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    // computeStatus() is called only if "status" is empty
    return status.orElseGet(this::computeStatus);
}

Item 5: quando nenhum valor está presente, lance uma exceção java.util.NoSuchElementException via orElseThrow() desde o Java 10

Usar o método Optional.orElseThrow() representa outra alternativa elegante ao isPresent()-get(). Às vezes, quando um valor Optional não está presente, tudo o que você quer fazer é lançar uma exceção java.util.NoSuchElementException. A partir do Java 10, isso pode ser feito pelo método orElseThrow() sem argumentos. Para o Java 8 e 9, considere o item 6.

Evitar:

// AVOID
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new NoSuchElementException();        
    }
}

Prefira:

// PREFER
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    return status.orElseThrow();
}

Item 6: quando nenhum valor está presente, lançar uma exceção explícita através de orElseThrow (fornecedor <? Extends X> exceptionSupplier)

No Java 10, para java.util.NoSuchElementException, use orElseThrow(), conforme mostrado no item 5.

O uso do método Optional.orElseThrow(Supplier<? extends X> exceptionSupplier) representa outra alternativa elegante ao isPresent()-get(). Às vezes, quando um valor Optional não está presente, tudo o que você quer fazer é lançar uma exceção explícita. Começando com o Java 10, se essa exceção for java.util.NoSuchElementException, basta confiar no método orElseThrow() sem argumentos - item 5.

Evitar:

// AVOID
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new IllegalStateException(); 
    }
}

Prefira:

// PREFER
public String findUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    return status.orElseThrow(IllegalStateException::new);
}

Item 7: Quando você tem uma referência opcional e precisa de uma referência nula, use orElse(null)

Quando você tem um Optional e precisa de uma referência nula, use orElse(null). Caso contrário, evite orElse(null).

Um cenário típico para usar orElse(null) ocorre quando temos um Optional e precisamos chamar um método que aceite referências nulas em certos casos. Por exemplo, vamos ver o Method.invoke() da API de Reflexão do Java. O primeiro argumento desse método é a instância do objeto na qual esse método específico deve ser chamado. Se o método é static, o primeiro argumento deve ser null.

Evitar:

// AVOID
Method myMethod = ... ;
...
// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> instanceMyClass = ... ;
...
if (instanceMyClass.isPresent()) {
    myMethod.invoke(instanceMyClass.get(), ...);  
} else {
    myMethod.invoke(null, ...);  
}

Prefira:

// PREFER
Method myMethod = ... ;
...
// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(instanceMyClass.orElse(null), ...);

Item 8: Consumir um Optional se estiver presente. Não faça nada se não estiver presente. Este é um trabalho para Optional.ifPresent().

O Optional.ifPresent() é uma boa alternativa para o isPresent()-get() quando você só precisa consumir o valor. Se nenhum valor estiver presente, não faça nada.

Evitar:

// AVOID
Optional<String> status = ... ;
...
if (status.isPresent()) {
    System.out.println("Status: " + status.get());
}

Prefira:

// PREFER
Optional<String> status ... ;
...
status.ifPresent(System.out::println);

Item 9: Consumir um Optional se estiver presente. Se não estiver presente, execute uma ação baseada em vazio. Este é um trabalho para Optional.ifPresentElse(), Java 9.

A partir do Java 9, o Optional.ifPresentOrElse() é uma boa alternativa para o isPresent()-get(). Isso é semelhante ao método ifPresent(), apenas que também cobre o ramo else.

Evitar:

// AVOID
Optional<String> status = ... ;
if(status.isPresent()) {
    System.out.println("Status: " + status.get());
} else {
    System.out.println("Status not found");
}

Prefira:

// PREFER
Optional<String> status = ... ;
status.ifPresentOrElse(
    System.out::println, 
    () -> System.out.println("Status not found")
);

Item 10: Quando o valor está presente, define/retorne essa opção. Quando nenhum valor está presente, defina/retorne o outro opcional. Este é um trabalho para Optional.or(), Java 9.

Às vezes, para Optional, Optional não vazio, queremos definir/retornar esse Optional. E quando o nosso Optional está vazio, queremos executar alguma outra ação que também retorna um Optional. Os orElse() e orElseGet() não podem realizar isso, pois ambos retornarão os valores desembrulhados. É hora de introduzir o método Optional.or() do Java 9, que é capaz de retornar um Optional descrevendo o valor. Caso contrário, retorna um Optional produzido pela função de fornecimento.

Evitar:

 // AVOID
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    if (status.isPresent()) {
        return status;
    } else {
        return defaultStatus;
    }  
}

Além disso, evite:

// AVOID
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    return status.orElseGet(() -> Optional.<String>of("PENDING"));
}

Prefiro:

// PREFER
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    return status.or(() -> defaultStatus);
    // or, without defining "defaultStatus"
    return status.or(() -> Optional.of("PENDING"));
}

Item 11: Optional.orElse/orElseXXX é uma substituição perfeita para isPresent()-get()

Isso pode ser usado para obter lambdas encadeados em vez de código interrompido. Considere ifPresent() e ifPresentOrElse() no Java 9 ou o método or() no Java 9 também.

Algumas operações específicas para lambdas estão retornando um Optional (por exemplo, findFirst(), findAny(), reduce(), …). Tentar usar este Optional através do isPresent()-get() é uma abordagem desajeitada, pois pode exigir que você quebre a cadeia de lambdas, polua o código com instruções if e considere a continuação da cadeia. Nesses casos, o orElse() e o orElseXXX() são muito úteis, pois podem ser encadeados diretamente na cadeia de expressões lambda e evitar códigos interrompidos.

Exemplo 1

Evitar:

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();
if (product.isPresent()) {
    return product.get().getName();
} else {
    return "NOT FOUND";
}

Além disso, evite:

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();
return product.map(Product::getName)
    .orElse("NOT FOUND");

Prefira:

// PREFER
List<Product> products = ... ;
return products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst()
    .map(Product::getName)
    .orElse("NOT FOUND");

Exemplo 2

Evitar:

// AVOID
Optional<Cart> cart = ... ;
Product product = ... ;
...
if(!cart.isPresent() || 
   !cart.get().getItems().contains(product)) {
    throw new NoSuchElementException();
}

Prefiro:

// PREFER
Optional<Cart> cart = ... ;
Product product = ... ;
...
cart.filter(c -> c.getItems().contains(product)).orElseThrow();

Item 12: Evitar Encadear os Métodos Opcionais com o Único Objetivo de Obter um Valor

Às vezes, tendemos a “usar demais” as coisas. O que significa que temos uma coisa, como Optional, e vemos um caso de uso em todos os lugares. No caso do Optional, um cenário comum envolve encadear seus métodos com o único objetivo de obter um valor. Evite essa prática e confie em código simples e direto.

Evitar:

// AVOID
public String fetchStatus() {
    String status = ... ;
    return Optional.ofNullable(status).orElse("PENDING");
}

Prefira:

// PREFER
public String fetchStatus() {
    String status = ... ;
    return status == null ? "PENDING" : status;
}

Item 13: Não Declarar Qualquer Campo Do Tipo Opcional

Não use Optional em métodos (incluindo setters ) ou argumentos de construtores.

Lembre-se que Optional não foi planejado para ser usado em campos e não implementa Serializable. A classe Optional definitivamente não é destinada para uso como uma propriedade de um Java Bean.

Evitar:

// AVOID
public class Customer {
    [access_modifier] [static] [final] Optional<String> zip;
    [access_modifier] [static] [final] Optional<String> zip = Optional.empty();
    ...
}

Prefira:

// PREFER
public class Customer {
    [access_modifier] [static] [final] String zip;
    [access_modifier] [static] [final] String zip = "";
    ...
}

Item 14: Não Use Opcional em Argumentos de Construtores

Não use Optional como campos ou em argumentos de métodos (incluindo setters).

Este é outro uso contra intenção Optional. Optional envolve objetos com outro nível de abstração, que, nesse caso, simplesmente adiciona código clichê extra.

Evitar:

// AVOID
public class Customer {
    private final String name;               // cannot be null
    private final Optional<String> postcode; // optional field, thus may be null
    public Customer(String name, Optional<String> postcode) {
        this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
        this.postcode = postcode;
    }
    public Optional<String> getPostcode() {
        return postcode;
    }
    ...
}

Prefira:

// PREFER
public class Customer {
    private final String name;     // cannot be null
    private final String postcode; // optional field, thus may be null
    public Cart(String name, String postcode) {
        this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
        this.postcode = postcode;
    }
    public Optional<String> getPostcode() {
        return Optional.ofNullable(postcode);
    }
    ...
}

Como você pode ver, agora o getter retorna um Optional. Não tome este exemplo como regra para transformar todos os seus getters assim. Na maioria das vezes, os getters retornam coleções ou matrizes e, nesse caso, preferem retornar coleções/matrizes vazias em vez de Optional. Use esta técnica e tenha em mente esta declaração de Brian Goetz:

Eu acho que rotineiramente usá-lo como um valor de retorno para getters seria definitivamente o uso excessivo.

Item 15: Não Use Opcional em Argumentos de Setters

Optional não se destina a uso como uma propriedade de um Java Bean ou como o tipo de propriedade persistente. Optional não é Serializable.

Usar Optional em setters é outro antipadrão. Comumente, eu o vi usado como o tipo de propriedade persistente (mapear um atributo de entidade como Optional). No entanto, usando Optional em entidades de modelo de domínio é possível, conforme mostrado no exemplo a seguir.

Evitar:

// AVOID
@Entity
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    ...
    @Column(name="customer_zip")
    private Optional<String> postcode; // optional field, thus may be null
     public Optional<String> getPostcode() {
       return postcode;
     }
     public void setPostcode(Optional<String> postcode) {
       this.postcode = postcode;
     }
     ...
}

Prefira:

// PREFER
@Entity
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    ...
    @Column(name="customer_zip")
    private String postcode; // optional field, thus may be null
    public Optional<String> getPostcode() {
      return Optional.ofNullable(postcode);
    }
    public void setPostcode(String postcode) {
       this.postcode = postcode;
    }
    ...
}

Item 16: Não Use Opcional em Argumentos de Métodos

Não force sites de chamadas para criar Optionals. Não use Optional como campos ou em argumentos de setters e construtores.

Usando Optional em argumentos de métodos é outro erro comum. Isso levaria a um código desnecessariamente complicado. Assuma a responsabilidade de verificar os argumentos em vez de forçar os sites de chamadas a criar os Optional. Essa prática desordena o código e pode causar dependência. Com o tempo, você vai usá-lo em todos os lugares. Tenha em mente que Optional é apenas outro objeto (um container) e não é barato - consome 4x a memória de uma referência simples! Mas você também pode querer verificar isso e decidir, dependendo do caso.

Evitar:

// AVOID
public void renderCustomer(Cart cart, Optional<Renderer> renderer,
                           Optional<String> name) {     
    if (cart == null) {
        throw new IllegalArgumentException("Cart cannot be null");
    }
    Renderer customerRenderer = renderer.orElseThrow(
        () -> new IllegalArgumentException("Renderer cannot be null")
    );    
    String customerName = name.orElseGet(() -> "anonymous"); 
    ...
}
// call the method - don't do this
renderCustomer(cart, Optional.<Renderer>of(CoolRenderer::new), Optional.empty());

Prefira:

// PREFER
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    if (cart == null) {
        throw new IllegalArgumentException("Cart cannot be null");
    }
    if (renderer == null) {
        throw new IllegalArgumentException("Renderer cannot be null");
    }
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);

Além disso, prefira (confie em NullPointerException):

// PREFER
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    Objects.requireNonNull(cart, "Cart cannot be null");        
    Objects.requireNonNull(renderer, "Renderer cannot be null");        
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);

Além disso, prefira (evite NullPointerException e use IllegalArgumentException ou outra exceção):

// PREFER
// write your own helper
public final class MyObjects {
    private MyObjects() {
        throw new AssertionError("Cannot create instances for you!");
    }
    public static <T, X extends Throwable> T requireNotNullOrElseThrow(T obj, 
        Supplier<? extends X> exceptionSupplier) throws X {       
        if (obj != null) {
            return obj;
        } else { 
            throw exceptionSupplier.get();
        }
    }
}
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    MyObjects.requireNotNullOrElseThrow(cart, 
                () -> new IllegalArgumentException("Cart cannot be null"));
    MyObjects.requireNotNullOrElseThrow(renderer, 
                () -> new IllegalArgumentException("Renderer cannot be null"));    
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);

Item 17: Não Use Opcional para Devolver Coleções Vazias ou Matrizes

Favor retornar uma coleção/matriz vazia. Com isso em mente, confie em Collections.emptyList(), emptyMap() e emptySet().

Para manter o código limpo e leve, evite retornar Optional para coleções/matrizes null ou vazias. Prefira retornar uma matriz ou coleção vazia.

Evitar:

// AVOID
public Optional<List<String>> fetchCartItems(long id) {
    Cart cart = ... ;    
    List<String> items = cart.getItems(); // this may return null
    return Optional.ofNullable(items);
}

Prefira:

// PREFER
public List<String> fetchCartItems(long id) {
    Cart cart = ... ;    
    List<String> items = cart.getItems(); // this may return null
    return items == null ? Collections.emptyList() : items;
}

Item 18: Evitar o Uso Opcional em Coleções

Geralmente, existem maneiras melhores de representar as coisas. Essa abordagem pode ser um cheiro de design.

Usar Optional nas coleções pode ser um vicio de design. Tome mais 30 minutos para pensar no problema e tenho certeza que você encontrará maneiras melhores. Mas, provavelmente, o argumento mais usado para sustentar essa abordagem refere-se ao Map. Parece assim: então, um Map retorna null se não houver mapeamento para uma chave ou se null for mapeado para a chave, então não posso distinguir se a chave não está presente ou é um valor ausente. Eu vou embrulhar os valores via Optional.ofNullable e pronto ! Bem, o que você fará se o seu Map de itens Optional for preenchido com valores null, valores Optional ausentes ou até mesmo Objetos Optional que contiverem algo mais, mas não seus extras? Você não acabou de aninhar o problema inicial em mais uma camada? Como sobre a penalidade de desempenho? Optional não é gratuito, é apenas outro objeto que consome memória e precisa ser coletado.

Evitar:

// AVOID
Map<String, Optional<String>> items = new HashMap<>();
items.put("I1", Optional.ofNullable(...));
items.put("I2", Optional.ofNullable(...));
...
Optional<String> item = items.get("I1");
if (item == null) {
    System.out.println("This key cannot be found");
} else {
    String unwrappedItem = item.orElse("NOT FOUND");
    System.out.println("Key found, Item: " + unwrappedItem);
}

Prefira (Java 8):

//PREFER
Map<String, String> items = new HashMap<>();
items.put("I1", "Shoes");
items.put("I2", null);
...
// get an item
String item = get(items, "I1");  // Shoes
String item = get(items, "I2");  // null
String item = get(items, "I3");  // NOT FOUND
private static String get(Map<String, String> map, String key) {
  return map.getOrDefault(key, "NOT FOUND");
}

Você também pode confiar em outras abordagens, como:

Extrapolando este exemplo para outras coleções, podemos concluir que sempre há soluções melhores do que usar o Optional em coleções.

E isso é ainda pior:

Map<Optional<String>, String> items = new HashMap<>();
Map<Optional<String>, Optional<String>> items = new HashMap<>();

Item 19: Não Confundir Optional.of() e Optional.ofNullable()

Confundir ou usar equivocadamente o Optional.of em vez do Optional.ofNullable, ou vice-versa, pode levar a problemas desagradáveis. Como uma chave aqui, tenha em mente que Optional.of(null) lançará NullPointerException, enquanto Optional.ofNullable(null) resultará em um Optional.empty.

Exemplo de uso Optional.of em vez de Optional.ofNullable

Evitar:

// AVOID
public Optional<String> fetchItemName(long id) {
    String itemName = ... ; // this may result in null
    ...
    return Optional.of(itemName); // this throws NPE if "itemName" is null :(
}

Prefira:

// PREFER
public Optional<String> fetchItemName(long id) {
    String itemName = ... ; // this may result in null
    ...
    return Optional.ofNullable(itemName); // no risk for NPE    
}

Exemplo de uso Optional.ofNullable em vez de Optional.of

Evitar:

// AVOID
return Optional.ofNullable("PENDING"); // ofNullable doesn't add any value

Prefira:

// PREFER
return Optional.of("PENDING"); // no risk to NPE

Item 20: Evitar Optional e escolher os não genéricos OptionalInt, OptionalLong ou OptionalDouble

A menos que você tenha uma necessidade específica de primitivas em mente, evite Optional<T> e selecione OptionalInt, OptionalLong ou OptionalDouble não genéricos.

Boxe e unboxing são operações caras que são propensas a induzir penalidades de desempenho. Para eliminar esse risco, podemos confiar em OptionalInt, OptionalLong e OptionalDouble. Esses são wrappers para tipos primitivos int,long, edouble`.

Evite (use-o somente se você precisar de primitivas em caixa):

// AVOID
Optional<Integer> price = Optional.of(50);
Optional<Long> price = Optional.of(50L);
Optional<Double> price = Optional.of(50.43d);

Prefira:

// PREFER
OptionalInt price = OptionalInt.of(50);           // unwrap via getAsInt()
OptionalLong price = OptionalLong.of(50L);        // unwrap via getAsLong()
OptionalDouble price = OptionalDouble.of(50.43d); // unwrap via getAsDouble()

Item 21: Não Há Necessidade de Desempacotar Opcionais para Afirmar a Igualdade

Ter dois Optionals em um assertEquals() não requer valores desembrulhados. Isso é aplicável porque Optional#equals() compara os valores agrupados, não os objetos Optional.

Código fonte Optional.equals():

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof Optional)) {
        return false;
    }
    Optional<?> other = (Optional<?>) obj;
    return Objects.equals(value, other.value);
}

Evitar:

// AVOID
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");        
assertEquals(expectedItem.get(), actualItem.get());

Prefira:

// PREFER
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");        
assertEquals(expectedItem, actualItem);

Item 22: Transformar valores via map() e flatMap()

O Optional.map() e o Optional.flatMap() são abordagens muito convenientes para transformar o valor Optional. O método map() aplica o argumento de função ao valor e, em seguida, retorna o resultado agrupado em um Optional, enquanto, por comparação, o método flatMap() aceita um argumento de função que é aplicado a um valor Optional e retorna o resultado diretamente.

Usando o map()

Exemplo 1

Evitar:

// AVOID
Optional<String> lowername ...; // may be empty
// transform name to upper case
Optional<String> uppername;
if (lowername.isPresent()) {
    uppername = Optional.of(lowername.get().toUpperCase());
} else {
    uppername = Optional.empty();
}

Prefira:

// PREFER
Optional<String> lowername ...; // may be empty
// transform name to upper case
Optional<String> uppername = lowername.map(String::toUpperCase);

Exemplo 2

Evitar:

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst();
String name;
if (product.isPresent()) {
    name = product.get().getName().toUpperCase();
} else {
    name = "NOT FOUND";
}
// getName() return a non-null String
public String getName() {
    return name;
}

Prefira:

// PREFER
List<Product> products = ... ;
String name = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst()
    .map(Product::getName)
    .map(String::toUpperCase)
    .orElse("NOT FOUND");
// getName() return a String
public String getName() {
    return name;
}

Usando flatMap()

Evitar:

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst();
String name = null;
if (product.isPresent()) {
    name = product.get().getName().orElse("NOT FOUND").toUpperCase();
}
// getName() return an Optional
public Optional<String> getName() {
    return Optional.ofNullable(name);
}

Prefira:

// PREFER
List<Product> products = ... ;
String name = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst()
    .flatMap(Product::getName)
    .map(String::toUpperCase)
    .orElse("NOT FOUND");
// getName() return an Optional
public Optional<String> getName() {
    return Optional.ofNullable(name);
}

Item 23: Rejeitar Valores Empacotados com Base em uma Regra Predefinida Usando filter()

Passar um predicado (a condição) como argumento e obter um objeto Optional. Use o Optional inicial se a condição for atendida e use um Optional vazio se a condição não for atendida.

Usar filter() para aceitar ou rejeitar um valor empacotado é uma abordagem muito conveniente, uma vez que pode ser realizado sem descompactar explicitamente o valor.

Evitar:

// AVOID
public boolean validatePasswordLength(User userId) {
    Optional<String> password = ...; // User password
    if (password.isPresent()) {
        return password.get().length() > 5;
    }
    return false;
}

Prefira:

// PREFER
public boolean validatePasswordLength(User userId) {
    Optional<String> password = ...; // User password
    return password.filter((p) -> p.length() > 5).isPresent();
}

Item 24: Precisamos encadear a API Optional com a API de fluxo?

Se assim for, então usamos o método Optional.stream().

A partir do Java 9, podemos tratar a instância Optional como um Stream aplicando o método Optional.stream(). Isso é útil quando você precisa encadear a API Optional com a API de fluxo. Esse método cria um Stream de um elemento ou um Stream vazio (se Optional não estiver presente). Além disso, podemos usar todos os métodos disponíveis na API do Stream.

Evitar:

// AVOID
public List<Product> getProductList(List<String> productId) {
    return productId.stream()
        .map(this::fetchProductById)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(toList());
}
public Optional<Product> fetchProductById(String id) {
    return Optional.ofNullable(...);
}

Prefira:

// PREFER
public List<Product> getProductList(List<String> productId) {
    return productId.stream()
        .map(this::fetchProductById)
        .flatMap(Optional::stream)
        .collect(toList());
}
public Optional<Product> fetchProductById(String id) {
    return Optional.ofNullable(...);
}

Praticamente, Optional.stream() nos permite substituir filter() e map() com flatMap().

Além disso, podemos converter Optional em List:

public static <T> List<T> convertOptionalToList(Optional<T> optional) {
    return optional.stream().collect(toList());
}

Item 25: Evite usar operações sensíveis à identidade em Optionals

Isso inclui igualdade de referência (==), identidade baseada em hash ou sincronização.

A classe Optional é uma classe baseada em valor como LocalDateTime.

Evitar:

// AVOID
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);
// op1 == op2 => false, expected true
if (op1 == op2) { ...

Prefira:

// PREFER
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);
// op1.equals(op2) => true,expected true
if (op1.equals(op2)) { ...

Nunca faça:

// NEVER DO
Optional<Product> product = Optional.of(new Product());
synchronized(product) {
    ...
}

Item 26: Retornar um booleano Se o Optional Estiver Vazio. Prefira o Optional.isEmpty() do Java 11

Começando com o Java 11, podemos facilmente retornar um true se um Optional estiver vazio através do método isEmpty().

Evite (Java 11+):

// AVOID (Java 11+)
public Optional<String> fetchCartItems(long id) {
    Cart cart = ... ; // this may be null
    ...    
    return Optional.ofNullable(cart);
}
public boolean cartIsEmpty(long id) {
    Optional<String> cart = fetchCartItems(id);
    return !cart.isPresent();
}

Prefir1 (Java 11+):

// PREFER (Java 11+)
public Optional<String> fetchCartItems(long id) {
    Cart cart = ... ; // this may be null
    ...    
    return Optional.ofNullable(cart);
}
public boolean cartIsEmpty(long id) {
    Optional<String> cart = fetchCartItems(id);
    return cart.isEmpty();
}

Bem, é isso! Parece que usar Optional corretamente não é tão fácil quanto parece à primeira vista. Principalmente, o Optional foi planejado para ser usado como um tipo de retorno e para combiná-lo com fluxos (ou métodos que retornam Optional) para construir APIs fluentes. Mas existem vários casos e tentações que podem ser considerados armadilhas que degradam a qualidade do seu código ou até mesmo causam comportamentos inesperados. Ter estes 26 itens sob o seu cinto de ferramentas será muito útil para evitar essas armadilhas, mas tenha em mente o que o Sr. Oliver Wendell Holmes, disse certa vez:

O jovem conhece as regras, mas o velho conhece as exceções.

Você se lembra do pequeno desenho animado no começo deste artigo? O que o VP do TPM fez de errado? Bem, o seu conselho de usar Optional está correto apenas que há uma falta de explicação. Não está claro se ele está pedindo ao desenvolvedor para refatorar o método fetch() para retornar Optional, ou para usá-lo como está, mas para encerrar sua chamada em um Optional. Obviamente, o desenvolvedor entendeu o segundo. O que o desenvolvedor e o revisor de código fizeram de errado? Ambos estão faltando do fato de que Optional.of(fetch()) lançará NPE se fetch() retornar null. Além disso, o código final está encadeando Métodos Optional of() e orElse() com o propósito único de retornar um valor em vez de retornar com base em uma simples verificação null (Item 12). No final, o código ainda é propenso ao NPE.

Uma das abordagens aceitáveis ​​consiste em refatorar o método fetch() para retornar Optional e do método pwd() para return, fetch().orElse("none").

Espero que isto ajude! Deixe-nos saber seus pensamentos na seção de comentários abaixo.