Java8-Optional容器使用

本文探讨了Java程序员如何通过使用Java 8的Optional类来优雅地处理和避免NullPointException,通过实例演示如何重构代码,提升健壮性,并介绍Optional的map、orElse、orElseGet和filter等方法在实际开发中的应用。
摘要由CSDN通过智能技术生成

前言

作为Java程序员,NullPointException是我们在开发调试中最常见的一个异常,原因也很简单,我们使用了一个没有被任何对应引用的值,它的值为null,所以在获取其对象时,系统会抛出NullPointException,就像这样:

public class TestNullPointException{
	@Test 
	public void test_null_point_exception(){
		UserInfo userInfo = getUserInfoFromRemote()
		String userName  = userInfo.getUserName;
		Assert.equeals(userInfo,"Jack");
	}
}

如何避免上述代码npe的发生?
为了让程序更加健壮,我们不得不对可能为null的引用进行判断,让以上代码不发生异常,我们得这样修改下:

public class TestNullPointException{
	@Test 
	public void test_null_point_exception(){
		UserInfo userInfo = getUserInfoFromRemote();
		String userName;
		if(userInfo!=null){
			userName= userInfo.getUserName()
		}else{
			userName="";
		}
		Assert.equeals(isEQ,false);	
	}
}

再比如有更加复杂的嵌套判断,如果有多个对象嵌套,我们要不断判断当前对象是否为null,然后再取它的引用。

  1. 我们来先引入一个嵌套的实体,以用户–>订单–>商品信息建模。获取该用户下某个订单的某个商品信息的名称。
//用户信息
 class UserInfo {
     private int userId;
     private OrderInfo orderInfo;
 }
//订单信息
class OrderInfo {
    private int orderId;
    private ProductInfo productInfo;
}
//商品信息
class ProductInfo {
    private int productId;
    private String productName;
    private double money;
}
  1. 获取商品名称的方法
private String getProductName(UserInfo userInfo) {
    if (userInfo != null) {
        OrderInfo orderInfo = userInfo.getOrderInfo();
        if (orderInfo != null) {
            ProductInfo productInfo = orderInfo.getProductInfo();
            if (productInfo != null) {
                return productInfo.getProductName();
            }
        }
    }
    return "";
}

以上代码为了获取商品名称,使用了三个判断null的嵌套操作,在代码优雅性和可读性上都不好,如果嵌套对象过多,这样的判断可能会更多,可能有比较优雅的写法,前置判断来过滤为null的值。

private String getProductNameByPreAssert(UserInfo userInfo) {
     if (userInfo == null) {
         return "";
     }
     OrderInfo orderInfo = userInfo.getOrderInfo();

     if (orderInfo == null) {
         return "";
     }
     ProductInfo productInfo = orderInfo.getProductInfo();
     if (productInfo == null) {
         return "";
     }
     return productInfo.getProductName();
 }

这样写不会有嵌套的null值判断,优雅性和逻辑都比第一种嵌套判断好一些,但是代码中还有很多null值判断,如果少了一环的判断,就可能出现NPE。有没有更好的方式去建模来避免NPE的出现?答案是有的,Java从8开始,内置了java.util.Optioal类来对可能为null的值进行建模,来尽可能避免NPE出现。

使用Optioal来替代null值的判断

在JDK中对Optional的描述中,Optional是一个容器,可能包含null值,或非null值,里面包含了对null值的封装的API。
在这里插入图片描述
其实使用Optional来对null值进行建模或者包装是对null自身设计"缺陷"的一个补丁,当然,这个补丁修补的不是bug,而是能让使用者的或者能让语言自身使用起来更易用,更优雅。
我们来使用Optional来对小结一中的例子二进行重构:

 private String getProductNameWithOptional(UserInfo userInfo) {
     return Optional.ofNullable(userInfo).map(UserInfo::getOrderInfo).map(OrderInfo::getProductInfo).map(ProductInfo::getProductName).orElse("");}

这个场景使用Optional是不是感觉比小结一种的代码清晰易懂,又简洁呢?
我们来逐步分析下以上代码,第一步,不能确认userInfo是否有值,所以我们使用Optioinal容器对其进行包装,得到一个Optional<UserInfo>的返回值,第二步我们使用Optional.map获取订单信息,同样得到的订单信息也会包装到Optional容器中,在②, ③,④中任意一部获取到的上一个引用值如果为null,则都会返回Optional.empty(),在⑤的时候会对结果进行判断,如果Optional里面的值存在,则返回,否则返回默认给定的空字符串。

Optional提供的其他API

除了上面使用到的Optional.mapOptional.orElse,Optional 还提供了其他方便快捷的API

Optional.orElseGet

和orElse比较类似,但是orElse不会延迟执行,不论Optional是否有值,而orElseGet会延迟执行,当Optional.empty()为true的时候才会执行传入的函数。

private OrderInfo getOrderInfo(UserInfo userInfo) {
    return Optional.ofNullable(userInfo)
            .map(UserInfo::getOrderInfo)
            .orElseGet(this::getOrderInfoFromRemote);
}
//远程调用获取OrderInfo
private OrderInfo getOrderInfoFromRemote() {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderId(1);
    orderInfo.setProductInfo(new ProductInfo());
    return orderInfo;
}

Optional.filter

filter操作和Stream的filter有异曲同工之妙,Stream中的filter是对流的操作,可以是一个元素也可以是n个元素,但是Optional.filter 是对Optional容器中的某个元素进行过滤。
🌰比如我们要对UserInfo(可能为null)中userId<=0的用户进行过滤,如果有小于0的则抛出异常:

private UserInfo filterUserId(UserInfo userInfo) {
    return Optional.ofNullable(userInfo)
            .filter(user -> user.getUserId() > 0)
            .orElseThrow(() -> new IllegalArgumentException("userId 不合法"));
}

Optional.flatMap

flatMap是扁平操作,故,有嵌套容器包装这的对象,需要将它结构成真正的对象。
在这里插入图片描述
🌰:我们需要根据UserInfo和ProductInfo获取productName,使用以下方法需要判断ProductInfo和UserInfo是否为空,UserInfo为空则直接返回空字符串

private String getProductNameWithUserInfoAndProductInfo(UserInfo userInfo, ProductInfo productInfo) {
    if (userInfo != null && productInfo != null) {
        return this.productName(userInfo, productInfo);
    }
    return "";
}
private String productName(UserInfo userInfo, ProductInfo productInfo) {
    return "mock Name";
}

使用Optional获取

private String getProductNameWithUserInfoAndProductInfoWithOpt(UserInfo userInfo, ProductInfo productInfo) {
    Optional<UserInfo> userInfoOpt = Optional.ofNullable(userInfo);
    Optional<ProductInfo> productOpt = Optional.ofNullable(productInfo);
    return userInfoOpt.flatMap(u -> productOpt.map(p -> productName(u, p)))
            .orElse("");

}

Optional.orElseThrow

我们想要从UseInfo中的OrderInfo下的ProductInfo中获取productName,如果其中某个对对象为空,则抛出响应的异常:

private String getProductNameWithOptional(UserInfo userInfo) {
    Optional<UserInfo> userInfoOpt = Optional.ofNullable(userInfo);
    return userInfoOpt.map(userInfo1 -> Optional.ofNullable(userInfo1.getOrderInfo()))
            .orElseThrow(() -> new RuntimeException("用户信息不存在"))
            .map(orderInfoOpt -> Optional.ofNullable(orderInfoOpt.getProductInfo()))
            .orElseThrow(() -> new RuntimeException("订单信息不存在"))
            .map(prodcutOpt -> Optional.ofNullable(prodcutOpt.getProductName()))
            .orElseThrow(() -> new RuntimeException("商品信息不存在"))
            .orElse("44234");
}

还有一些其他的API就不一一介绍了,学到了上面的API,其他的看官方文档文档应该也能很快理解。

注意事项

总结

Java在JDK8 以上为Java语言的判空操作提供了安全的Optional容器,帮助我们在操作对象时避免NPE异常,阅读Optional类提供的方法我们也可以发现,每一个对Optional的操作其实JDK帮我们判断了当前操作的对象是否为空。
比如:Optional.map

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

再比如Optional.filter

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
        return this;
    } else {
        return predicate.test(value) ? this : empty();
    }
}

但是在使用是也需要注意,不是所有的对象和操作用Optional容器包装起来都是安全或者最好的做法,应该按照实际情况,合理的使用Optional类提供的方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值