I. Visão geral
Acredito que NullPointerException é familiar a todo programador JAVA e é a exceção mais comum em aplicativos JAVA. Anteriormente, o projeto Google Guava propunha o uso da classe Opcional para agrupar objetos para resolver NullPointerException. Afetado por isso, a classe Opcional também foi introduzida nas classes JDK8, e o suporte para este método foi implementado na nova versão do SpringData Jpa e Spring Redis Data.
2. Aula opcional
/**
* A container object which may or may not contain a non-null value.
* If a value is present, {@code isPresent()} will return {@code true} and
* {@code get()} will return the value.
*
* @since 1.8
*/
public final class Optional<T> {
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();
/**
* If non-null, the value; if null, indicates no value is present
*/
private final T value;
// 其他省略
}
A anotação deste método significa aproximadamente: Opcional é um objeto contêiner, que pode conter valores nulos ou valores não nulos. Quando o valor do atributo é definido, o método isPesent() retornará verdadeiro e o método get() retornará este valor. Esta classe suporta genéricos, ou seja, seu valor de atributo pode ser uma instância de qualquer objeto.
3. Métodos de classe opcional
número de série | método | Descrição do método |
---|---|---|
1 | private Optional() |
Construção sem parâmetro, constrói um Opcional vazio |
2 | private Optional(T value) |
Construa um Opcional com base no valor não vazio passado |
3 | public static<T> Optional<T> empty() |
Retorna um Opcional vazio, o valor desta instância está vazio |
4 | public static <T> Optional<T> of(T value) |
Construa um Opcional com base no valor não vazio passado, que tem o mesmo efeito que o método Opcional (valor T) |
5 | public static <T> Optional<T> ofNullable(T value) |
Diferente do método of(T value), ofNullable(T value) permite que você passe um valor vazio. Quando o valor passado é um valor nulo, ele cria um opcional vazio. Quando o valor passado não está vazio, ele é o mesmo que de. () tem o mesmo efeito |
6 | public T get() |
Retorna o valor de Opcional. Se o contêiner estiver vazio, uma NoSuchElementException será lançada. |
7 | public boolean isPresent() |
Determine se o valor do Opcional do proprietário foi definido |
8 | public void ifPresent(Consumer<? super T> consumer) |
Determine se o Opcional do proprietário definiu um valor. Se houver um valor, chame a interface funcional do Consumidor para processamento. |
9 | public Optional<T> filter(Predicate<? super T> predicate) |
Se um valor for definido e as condições de julgamento do Predicado forem atendidas, o Opcional será retornado, caso contrário, um Opcional vazio será retornado. |
10 | public<U> Optional<U> map(Function<? super T, ? extends U> mapper) |
Se Opcional definir um valor, chame Function para processar o valor e retornar um Opcional contendo o valor processado, caso contrário, um Opcional vazio será retornado. |
11 | public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) |
Diferente do tipo de método map(), o resultado do mapeador já é Opcional e não há necessidade de agrupar o resultado. |
12 | public T orElse(T other) |
Se o valor Opcional não estiver vazio, retorne esse valor, caso contrário, retorne outro |
13 | public T orElseGet(Supplier<? extends T> other) |
Se o valor Opcional não estiver vazio, retorne este valor, caso contrário gere outro baseado em outro |
14 | public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)throws X |
Se o valor Opcional não estiver vazio, retorne o valor, caso contrário, lance uma exceção por meio do fornecedor |
4. Exemplos de métodos da classe Opcional
Exemplo um:
public static void main(String[] args) {
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
}
resultado da operação:
Full Name is set? false
Full Name: [none]
Hey Stranger!
ilustrar:
ifPresent()方法当Optional实例的值非空时返回true,否则返回false;
orElseGet()方法当Optional包含非空值时返回该值,否则通过接收的function生成一个默认的;
map()方法转换当前Optional的值,并返回一个新的Optional实例;
orElse()方法与orElseGet方法相似,不同的是orElse()直接返回传入的默认值。
Exemplo dois:
Modifique o Exemplo 1 para gerar uma instância Opcional com um valor não nulo.
Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
resultado da operação:
First Name is set? true
First Name: Tom
Hey Tom!
A diferença do Exemplo 1 pode ser vista claramente. Isso não apenas simplifica nosso código, mas também torna-o mais fácil de ler.
5. Código-fonte opcional
Vamos dar uma olhada no código-fonte de vários métodos usados no exemplo:
de
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
é presente
public boolean isPresent() {
return value != null;
}
ou entãoObtenha
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
se não
public T orElse(T other) {
return value != null ? value : other;
}
6. Use Opcional para evitar ponteiros nulos
Em nosso processo de desenvolvimento diário, inevitavelmente encontraremos problemas de ponteiro nulo. No passado, quando ocorriam problemas de ponteiro nulo, geralmente precisávamos depurar e outros métodos para finalmente localizar o local específico, especialmente ao chamar entre serviços de sistema distribuído. O problema é mais difícil de localizar. Depois de usar Opcional, podemos empacotar o objeto de parâmetro recebido. Por exemplo, o serviço de pedido precisa chamar uma interface do serviço do produto e passar as informações do produto por meio dos parâmetros. Nesse momento, os parâmetros do produto recebido podem ser passados diretamente. É nulo. Neste momento, o método do produto pode usar opcional.of(T) para agrupar o objeto recebido. Se T estiver vazio, uma exceção de ponteiro nulo será lançada diretamente. Quando vemos as informações da exceção, podemos saber imediatamente que ocorreu um ponteiro nulo. O motivo é que o parâmetro T está vazio; ou, quando o parâmetro de entrada está vazio, podemos usar o método Opcional.orElse() ou Opcional.orElseGet() para gerar uma instância padrão e então executar operações subsequentes.
Vejamos um exemplo específico: há uma classe Address na classe User, uma classe Street na classe Address e um atributo streetName na classe Street. O requisito atual é: obter o streetName correspondente com base na instância de User recebida. Se If User for nulo ou Address for nulo ou Street for nulo, "nada encontrado" será retornado, caso contrário, o streetName correspondente será retornado.
1. Maneira normal
@Data
public class User {
private String name;
private Integer age;
private Address address;
}
@Data
public class Address {
private Street street;
}
@Data
public class Street {
private String streetName;
private Integer streetNo;}
public String getUserSteetName(User user) {
if(null != user) {
Address address = user.getAddress();
if(null != address) {
Street street = address.getStreet();
if(null != street) {
return street.getStreetName();
}
}
}
return "nothing found";
}
2.Use Opcional
O problema óbvio na implementação é que o nível de julgamento é muito profundo.As seguintes reutilizações são opcionais para reescrever:
@Data
public class User {
private String name;
private Integer age;
private Optional<Address> address = Optional.empty();
}
@Data
public class Address {
private Optional<Street> street = Optional.empty();
}
@Data
public class Street {
private String streetName;
private Integer streetNo;
}
public String getUserSteetName(User user) {
Optional<User> userOptional = Optional.ofNullable(user);
final String streetName = userOptional.orElse(new User()).getAddress().orElse(new Address()).getStreet().orElse(new Street()).getStreetName();
return StringUtils.isEmpty(streetName) ? "nothing found" : streetName;
}
Usar o método orElse() para fornecer um valor padrão garante que o problema do ponteiro nulo não será relatado e que os requisitos também poderão ser atendidos.