写作背景
- 在看到Optional这一新特性时,我就迫不及待的通过搜索引擎学习了一波。但是各种教程所提供的示例,让我完全感觉不到Optional的方便之处,甚至让我产生了一种错觉,觉得这个新特性是累赘的,完全不必要的。
- 在深入源码以及经过一段时间的摸索和使用后,也算是对Optional有了比较浅显的理解。Optional作为npe的解决方案,其实效果还是特别明显的,能很大程度上减少npe异常发生的频率,可以让源码更易于阅读。借着闲时,把自己用到的Optional方法总结下来,以供后续查阅。
无效示例1
你是否看过这样的示例?
public void test(String userName) {
Optional<String> options = Optional.ofNullable(userName);
if (options.isPresent()) {
String s = options.get();
// do something
}
}
如果单单只是判断是否为null,那么直接用==更方便,大可不必使用Optional,这既没有展示Optional的特性,也体现不了Optional的优势。
无效示例2
又或者这样的示例:
public void test(String userName) {
Optional<String> optional = Optional.of(userName);
optional.ifPresent(System.out::println);
}
一些文章为了将每一个Optional的每一个方法解释清楚,便将每个方法都单独写了一个示例。然而实际的使用是需要将Optional的多个方法联系起来才能最大发挥它的作用,单个示例就导致它们的剥离感严重,根本无法与实际业务结合起来使用,导致参考意义下降。
因此本文将不会对Optional的各个方法进行详细的阐述,只会说明Optional在实际场景中如何使用,并提及一些Optional方法的注意事项。
运用场景
Optional的作用:
- 开头提到了,Optional作为npe(NullPointerException)的解决方案,它的最主要作用就是保证操作一个null对象时,流程依旧会流转下去,不会不经开发者同意就向上抛出npe(是否抛取决于开发者的业务)。可以把这种功能简单的理解为Optional的每一步操作都会进行一个非空校验,空和非空会各自执行一个分支语句。
注意事项:
- 如果已知对象不可能为空,则尽量不要使用这个类来增加代码的复杂度。
- 如果条件判断比较简单,一般也不会用到Optional。
- 鉴于大多数情况下,开发者都不会主动去校验对象是否为null,从而导致程序中断,所以一般推荐将Optional用于查询结果的非空校验。
示例类关系示例
Product:
public class Product {
private Long id;
/**
* 商品详情
*/
private ProductDetail productDetail;
private Long categoryId;
/**
* 分类信息
*/
private Category category;
private Long brandId;
/**
* 品牌
*/
private Brand brand;
}
ProductDetail:
public class ProductDetail {
private Long id;
/**
* 注意事项
*/
private String description;
/**
* 商品图片
*/
private List<ProductPicture> productPictures;
}
1. if-else操作
是Optional实际运用的基础操作,自然地,代码里的任意if-else非空判断都能与Optional相互替代。
注意事项:
- 当orElse通过方法来获取默认值时,orElse中的方法将总是会被执行(因为该方法接受的是一个具体值,为了获取该具体值,总是会在调用orElse方法前执行其参数中的方法)
- orElseGet则不同,它只在值为空时才会执行该方法(它的参数是一个方法引用,而非具体值)
- 如果只是需要一个固定的默认值,可以使用orElse;当需要方法或数据库执行结果时,都推荐使用orElseGet。
Optional写法:
public static Product selectProductOtherwiseDefault(Long productId) {
Optional<Product> optional = Optional.ofNullable(getProduct(productId));
optional.ifPresent(OptionalUsage::setProductInfo);
return optional.orElse(getDefaultProduct());
// return optional.orElseThrow(BusinessException::new);
// return optional.orElseGet(OptionalUsage::getDefaultProduct);
}
if-else写法:
public static Product selectProductOtherwiseDefault1(Long productId) {
Product product = getProduct(productId);
if (product != null) {
setProductInfo(product);
} else {
product = getDefaultProduct();
// throw new BusinessException();
}
return product;
}
2. 对象属性非空校验
如果要获取对象的属性,我们不知道这个对象是否为null,以及它的属性是否为null时,可以使用Optional来代替那些非空校验。
注意事项:
- map方法映射的结果仍然会被Optional封装,后续仍能对该方法返回的null值进行操作
- flatMap则是直接返回的映射结果,如果结果为null,该方法会抛出npe
Optional写法:
public static List<ProductPicture> getProductPicture(Product product) {
Optional<Product> optional = Optional.ofNullable(product);
return optional
.map(Product::getProductDetail)
.map(ProductDetail::getProductPictures)
.orElse(null);
}
非空校验写法:
public static List<ProductPicture> getProductPicture1(Product product) {
if (product != null) {
ProductDetail productDetail = product.getProductDetail();
if (productDetail != null) {
return productDetail.getProductPictures();
}
}
return null;
}
对象层级越多,这种写法就越显得臃肿、越难于阅读。
3. 嵌套非空校验
多个对象嵌套,在不清楚某个对象是否为null时,想对某个嵌套对象做某些操作,就可以用到Optional。
Optional写法:
public static void setProductInfo(Product product) {
Optional<Product> optional = Optional.ofNullable(product);
optional.ifPresent(product1 -> {
// getBrand一般代表其它方法的结果或sql查询结果
product1.setBrand(Optional.ofNullable(product1.getBrandId()).map(OptionalUsage::getBrand).orElse(null));
product1.setCategory(Optional.ofNullable(product1.getCategoryId()).map(OptionalUsage::getCategory).orElse(null));
});
}
非空校验写法:
public static void setProductInfo1(Product product) {
if (product != null) {
Long brandId = product.getBrandId();
Long categoryId = product.getCategoryId();
if (brandId != null) {
product.setBrand(OptionalUsage.getBrand(brandId));
}
if (categoryId != null) {
product.setCategory(OptionalUsage.getCategory(categoryId));
}
}
}
这种写法很常见,但是大多数开发者都可能会忘记做这一部分的非空校验,从而导致下游服务或方法抛出npe异常,从而中断整个程序的执行。
4. 其它校验与非空校验组合
其实大部分情况下,我们除了校验对象是否为空,还会对它的某个属性进行校验,从而来组成一个完整的if-else组合。如果Optional仅支持非空校验,那么使用场景其实有限的,索性Optional还支持非空校验外的其它校验来过滤数据。
Optional写法:
// 通过校验后,仍然可以像前几列一样执行if-else操作
public static boolean checkProductIntegrityThenUpdate(Product product) {
Optional<Product> optional = Optional.ofNullable(product)
.filter(product1 -> null != product.getProductDetail() && null != product.getCategory() && anyOtherCheck());
optional.ifPresent(OptionalUsage::updateIntegrityProductInfo);
return optional.isPresent();
}
组合校验写法:
public static boolean checkProductIntegrityThenUpdate1(Product product) {
boolean check = product != null && null != product.getProductDetail() && null != product.getCategory() && anyOtherCheck();
if (check) {
updateIntegrityProductInfo(product);
}
return check;
}