Optional是在 Java8中引入的新特性之一。使用Optional类包装数据,可以避免经典的空检查和一些try-catch代码块。也能够通过链式方法调用,写出更流畅的函数式编程的代码。另一方面,滥用Optional也会导致性能低下和代码混乱。过往项目业务中有大量使用到,现做个归纳总结如下。
//消息实体转换
refundMQModel = Optional.ofNullable(message)
.map(e -> JsonUtil.fromson(e, RefundMQModel.class ))
.filter(e -> e.smsOrderNo != null)
.orElseThrow(()->new VerifyException(message ,"The smsOrderNo does not exist"));
//获取订单信息
SmsFlowModel smsFlowModel = RefundUtil.queryOrder(refundMOModel.smsOrderNo)
.orElseThrow(()-> new VerifyException(message ,"Order info does not exist"));
//是否需要退费
RefundUtil.whetherToRefund(smsFlowModel).filter(e ->e.equals(Boolean.TRUE))
.orElseThrow(()->new VerifyException(message ," No refund required"));
//获取退费记录,不存在则自动创建
RefundDBModel dbRefundMQModel =
RefundUtil.getOrCreateRefundRecord(smsFlowModel.getSmsOrderNo())
.map(e -> e.withOrderInfo(smsFlowModel))
.orElseThrow(()-> new BusinessException(message ,"Failed to get the refund record"));
//调用外部退费接口
Optional.ofNullable(dbRefundMQModel)
.filter(e -> e.isNeedsCallOperator())
.ifPresent(refund ->{
//状态机(乐观锁)&&变更状态为处理中
RefundUtil.update0peratorStatusByOrderNo(refund .getSms0rderNo(),
OperatorRefundStatusEnum.REFUNDING.getValue(),
OperatorRefundStatusEnum.TO_BE_REFUND.getValue());
//调用退费接口&&变更为已完成
RefundUtil.callOperatorRefund(refund);
});
//开启平台内退费
Optional.ofNullable(dbRefundMQModel)
.filter(e -> e.isNeedsToRefund())
.ifPresent(refund ->{
//状态机(乐观锁)&&变更状态为处理中
RefundUtil.updateRefundStatusByOrderNo(refund.getSmsOrderNo(),
RefundStatusEnum.REFUNDING.getValue(),
OperatorRefundStatusEnum.TO_BE_REFUND.getValue());
//平台正式退费&&变更为已完成
RefundUtil . callPlatformRefund ( refund );
});
1. empty()
返回一个Optional容器对象,而不是 null。
public Optional<User> getUser(String name) {
if (StringUtil.isNotEmpty(name)) {
return RemoteService.getUser(name);
}
return Optional.empty();
}
2. orElse()
返回Optional中包装的值,取不到值时返回指定的 default。
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse(USER_STATUS);
}
//不要这么写
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse("UNKNOWN");//这样每次都会新建一个String对象
}
orElse()无论如何都会执行括号中的内容。适用于返回静态资源、字符串等。远程调用,或者涉及大量的文件 IO,代价巨大。
3. orElseGet()
返回Optional中包装的值,取不到值时返回指定的 default。
public String getName() {
System.out.print("method called");
}
String name1 = Optional.of("String").orElse(getName()); //output: method called
String name2 = Optional.of("String").orElseGet(() -> getName()); //output:
3. orElseThrow()
针对阻塞性的业务场景使用比较合适,例如没有从上游获取到用户信息,下面的所有操作都无法进行,那此时就应该抛出异常。正常的写法是先判空,再手动 throw 异常。
public String findUser(long id){
Optional<User> user = remoteService.getUserById(id);
return user.orElseThrow(IllegalStateException::new);
}
4. ifPresent()
不为空则执行时,使用 ifPresent()。没有性能上的优势,只是使代码更简洁。
Optional<Integer> status = Optional.ofNullable(tokenService.getUserId("id"));
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
//现在
status.ifPresent(System.out::println);
5.使用示例如下
//传统写法
public Account getAccountClassic() {
Account account = accountRepository.get("jack");
if(account == null) {
throw new AccountNotFound();
}
return account;
}
// Optional写法
public Account getAccountOptional() {
return accountRepository.find("jack")
.orElseThrow(AccountNotFound::new);
}
//传统写法
public AccountHolder getAccountHolderClassic() {
Account account = accountRepository.find("jack");
if (account == null) {
throw new AccountNotFound();
}
return account.getHolder();
}
// Optional写法
public AccountHolder getAccountHolder() {
return accountRepository.find("jack")
.map(Account::getHolder)
.orElseThrow(AccountNotFound::new);
}
6. 注意不要滥用
使用Optional包装简单的局部变量
在Optional中包装变量只是为了利用它的API进行简单操作。反例:
Optional.ofNullable(account)
.ifPresent(acc-> processAccount(acc));
这里使用Optional没有带来任何价值。在这种情况下,我们应该使用经典的空检查:
if (account != null) {
processAccount(account);
}
使用经典的方式,可以增加代码的可读性,也会减少Optional对象的创建。
使用Optional包装字段
使用Optional包装字段,将导致在不需要对象的地方创建对象,如果重复使用,则会导致性能下降。此外,Optional包装的字段也不能进行序列化,因此,使用Optional字段可能会导致序列化问题。
一般来说,对于 POJO 中的 getter,更适合返回实际类型,而不是Optional类型。特别是,对于实体 bean、data module和 DTO 来说,建议都使用传统的 getter。反例:
1.作为序列化类中的字段
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account implements Serializable {
@IdprivateString id;
private Optional<String> number;
privateString holder;
}
// 执行序列化操作
public static void main(String[] args) throws IOException {
new ObjectOutputStream(new ByteArrayOutputStream())
.writeObject(Account
.builder()
.number(Optional.of("123"))
.build());
}
// 将会抛出异常
Exception in thread "main" java.io.NotSerializableException: java.util.Optional
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1187)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1572)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1529)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1438)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1181)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:350)
at com.example.demo.DemoApplication.main(DemoApplication.java:18)
2..Json转换
publicclass Account implements Serializable {
private String number;
public Optional<String> getNumber() {
return Optional.ofNullable(number);
}
publicvoid setNumber(Stringnumber) {
this.number = number;
}
}
使用Json序列化时,我们得到的结果是:
{"number":{"present":true}}
但是我们期望的结果是:
{"number":"123456"}
JPA entity中使用Optional字段
@Data
@Entity
public class Account implements Serializable{
@Idprivate Long id;
private Optional<String> number;
}
3.启动时,spring boot会直接报错
2023-03-05T08:08:26.103+08:00 ERROR 68105 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean withname'entityManagerFactory' defined inclasspathresource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Could not determine recommended JdbcType for`java.util.Optional<java.lang.String>`
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1752) ~[spring-beans-6.0.5.jar:6.0.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.0.5.jar:6.0.5]
xxx
xxx
xxx
Optional作为方法参数
public Account createAccount(Stringnumber, Optional<String> holder) {
// todo save
}
上面例子中,参数holder可以为空。很显然,采用方法重载的方式要比这种方式更清晰。
public Account createAccount(Stringnumber)
public Account createAccount(Stringnumber, String holder)