合理利用Optional 来避免NPE

一、什么是Optional

在Java中什么异常最容易出现,那肯定是NullPointerException,空指针就像一个定时炸弹,总给我们带来些麻烦,在开发过程中都会碰到需要判断Null值以防止空指针的情况,以往的方式要么是抛异常,要么是if{}else{},直到Optional的出现,你可以更优雅的解决NPE问题。
首先我们来看一下 Optional的作者 Brian Goetz 对这个 API 的说明:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”, and using null for such was overwhelmingly likely to cause errors.

翻译过来大致意思:Optional提供了一个有限的机制让类库方法返回值清晰的表达有与没有值,避免很多时候 null 造成的错误
所以尝试在Java8+中使用工具类Optional,并在特定场景中简化代码逻辑。

二、Optional中有哪些常用方法

静态方法

  • Optional.of(T value):为指定的值创建一个指定非 null 值的 Optional,需要注意的是传入的参数不能为 null,否则抛出 NullPointerException。
  • Optional.ofNullable():为指定的值创建一个 Optional 对象,如果指定的参数为 null,不抛出异常,直接则返回一个空的 Optional 对象。

对象方法

  • isPresent():判断optional是否为空,如果空则返回false,否则返回true
  • get():如果 Optional 有值则将其返回,否则抛出 NoSuchElementException 异常
  • ifPresent(Consumer c):如果optional不为空,则将optional中的对象传给Comsumer函数orElse(T other):如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值
  • orElseGet(Supplier other):如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other
  • orElseThrow(Supplier exception):如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
  • filter(Predicate p):如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional
  • map(Function<T, U> mapper):如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中。
  • flatMap(Function< T,Optional< U >> mapper):跟上面一样,在optional不为空的情况下,将对象t映射成另外一个optional,区别:map会自动将u放到optional中,而flatMap则需要手动给u创建一个optional

三、Optional误用场景

1. 直接使用 isPresent() 进行 if 检查

这么写和直接判断空没啥区别,反而增加了麻烦

public class User{
	private String name;
	private Integer age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
// 错误使用Optional
	User u=null;
	Optional<User> userOpt=Optional.ofNullable(u);
	if(userOpt.isPresent()){
		String name=u.getName();
		System.out.println(name);
	}
	// 直接判断
	if(u !=null){
		String name=u.getName();
		System.out.println(name);
	}

可以接受的写法:建议使用 ifPresent(Consumer<? super T> consumer),判断Optional中是否有值,有值则执行 consumer,否则什么都不干,不建议直接使用isPresent() 进行 if 检查进行判断

userOpt.ifPresent(user -> System.out.println(user.getName()));

其实isPresent() 更建议用在流处理的结尾,用于判断是否符合条件

list.stream()
    .filer(x -> Objects.equals(x,param))
    .findFirst()
    .isPresent()

2. 在方法参数中使用 Optional或者在POJO中使用

Optional本身并没有实现序列化,现有的 JSON 序列化框架大多数也没有对此提供支持的。

public class User {
    private String name;
	private Integer age;
    // 不建议
    private Optional<String> address;
}

3. 直接使用 Optional.get

Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用 Optional.get()和不做任何空判断一样,十分危险,当没有值时会抛出一个NoSuchElementException异常

User u=null;
Optional<User> userOpt=Optional.ofNullable(u);
// 不建议
System.out.println(userOpt.get().getName());

得到得结果是:
在这里插入图片描述
获取 value 有三种方式:get() 、orElse()、 orElseGet()
get()不能直接使用,需要结合判空使用。这和!=null其实没多大区别,只是在表达和抽象上有所改善。
orElse() 和ElseGet() 区别
性能问题:无论如何都会执行括号中的内容, orElseGet()只在主体 value 是空时执行orElseGet()需要构建一个Supplier
使用不当,再次空指针,当orElse的参数是间接计算得来的时候。虽然这种说法有点牵强(因为并不是orElse导致了空指针异常),但是使用orElseGet确实可以避免这种情况

class User {
    // 中文名
	private String chineseName;
	// 英文名
	private EnglishName englishName;
}

class EnglishName {
    // 全名
    private String fullName;
    // 简写
    private String shortName;
}

假如我们现在有User类,用户注册账号时,需要提供自己的中文名或英文名,或都提供,我们抽象出一个EnglishName类,它包含英文名的全名和简写(因为有的英文名确实太长了)。现在,我们希望有一个User#getName()方法,它可以像下面这样实现:当用户只提供了中文名时,此时englishName属性是null,但是在orElse中,englishName.getShortName()总是会执行。而在getName2()中,这个风险却没有。

class User {
    // ... 之前的内容
    public String getName1() {
        return Optional.ofNullable(chineseName)
                .orElse(englishName.getShortName());
    }
    public String getName2() {
        return Optional.ofNullable(chineseName)
                .orElseGet(() -> englishName.getShortName());
    }
}

如果只是简单的返回一个静态资源、字符串等等,直接返回静态资源使用orElse()即可。

4. 使用在注入的属性中

// 一般不建议
public class CommonService {
    private Optional<UserService> userService;
}

四、Optional正确使用姿势

1.将对象某个属性赋值给其他实体对象

// 调用服务层查询数据库获得目标对象
ProdSku prodSku = prodSKUService.getFirstProdSku(p.getProdId());
if (prodSku != null) {
    prodAPP.setPrice(prodSku.getPrice());
}
// 使用Optional 和函数式编程,一次完成
Optional.ofNullable(prodSku).ifPresent(p -> prodAPP.setPrice(p.getPrice()));

2.当取不到值时,返回你指定的 default

如果指定值固定的,明确的建议用orElse(),如果指定值是不明确的或者是需要大量计算得出的建议用orElseGet()

2.1判断一个集合大小,但又怕这个集合是null

List<User> list=null;
Optional<List<User>> optional=Optional.ofNullable(list);
int size = optional.map(List::size).orElse(0);
// 输出0
System.out.println(size);
// 直接调用集合的size方法就有可能造成 NullPointerException 空指针异常
System.out.println(list.size());

2.2实体类get方法初始化赋值

// 当传给前端同学一个字段时候,并不希望这个值出现null,但数据库里确实就是null的情况
public BigDecimal getAccount() {
        if (account == null) {
            account = BigDecimal.ZERO;
        }
        return account;
}
// 代替:
public BigDecimal getAccount() {
        return Optional.ofNullable(account).orElse(BigDecimal.ZERO) ;
}

2.3 类型之间转换,当没有值时候就返回一个默认值

String price=null;
Double c=Optional.ofNullable(price).map(a->Double.valueOf(a)).orElse(0.00);
System.out.println(c);
// 直接转换必定空指针错误
Double b=Double.valueOf(price);

2.4获取对象对应的汉字信息

如果是null就直接返回不获取,不论是a是null,还是对a运算是null,都能走到orElse 里面去的

Optional<Integer> optional=Optional.ofNullable(super.getAduitStatus());
        return optional.map(a->AduitStatusEnum.name(String.valueOf(a))).orElse(null);

3.多层属性获取(获取一个多层嵌套实体的值,收益最明显的一个例子)

 //+++++++++++++++++++++++模拟这个要取的对象从其他接口传过来是不是null++++++++++++++++++++++++++++++++++++
    InetSocketAddress inetSocketAddress = new InetSocketAddress(2);
    String re="小明取不到值";
    if(inetSocketAddress !=null){
        if(inetSocketAddress.getAddress() !=null){
            if(inetSocketAddress.getAddress().getHostName() !=null){
                String name2 = inetSocketAddress.getAddress().getHostName().toUpperCase();
                if(StringUtils.isNotBlank(name2) ){
                    re=name2;
                }
            }
        }
    }
    System.out.println(re);

    Optional<InetSocketAddress> optional=Optional.ofNullable(inetSocketAddress);
    String op= optional.map(a ->a.getAddress()).map(b ->b.getHostName()).map(c->c.toUpperCase()).orElse("小王也取不到值");
    System.out.println(op);

 //+++++++++++++++++++++++模拟这个要取的对象从其他接口传过来是null++++++++++++++++++++++++++++++++++++
    String re2="小明取不到值";
    InetSocketAddress inetSocketAddress2 = null;
    if(inetSocketAddress2 !=null){
        if(inetSocketAddress2.getAddress() !=null){
            if(inetSocketAddress2.getAddress().getHostName() !=null){
                String name2 = inetSocketAddress2.getAddress().getHostName().toUpperCase();
                if(name2 ==null){
                    re2=name2;
                }
            }
        }
    }
    System.out.println(re2);

    Optional<InetSocketAddress> optional2=Optional.ofNullable(inetSocketAddress2);
    String op2= optional2.map(a ->a.getAddress()).map(b ->b.getHostName()).map(c->c.toUpperCase()).orElse("小王也取不到值");
    System.out.println(op2);

运行结果如下图所示
在这里插入图片描述

4.阻塞性业务场景推荐使用

BigDecimal stock = Optional.ofNullable(lcStockObj).map(a -> a.getAssetNum())
                           .orElseThrow(() -> new RuntimeException("库存数据不存在,出入库失败"));

参考链接:https://blog.csdn.net/rhythm_1/article/details/121716010

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一棵小白菜#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值