Java 8 Optional在实际业务场景中的运用

写作背景

  • 在看到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;
}



示例代码源码

猜你喜欢

转载自blog.csdn.net/HO1_K/article/details/128930657
今日推荐