Optional类与使用==判断null有什么区别?使用Optional类有什么优势?

1、使用object==null的例子

2、null带来的问题

3、其他语言中null的处理(替代)

4、Java8的Optional类

  4.1 这样做有什么好处呢?

  4.2 引入Optional类的目的

  4.3 null与Optional.empty()

  4.4 使用Optional

  4.5 使用Optional域,该域无法序列化

  4.6 应用

参考文献

 

1、使用object==null的例子

例1

pulbic String getCarInsuranceName(Person person){
    if(person != null){
        Car car = person.getCar();
        if(car != null){
            Insurance insurance = car.getInsurance();
            if(insurance != null){
                return insurance.getName();
            }
        }
    }
    return "UNKNOWN";
}

可以发现这样写比较繁琐,每当某个变量可能为null时,都要添加if块来判断,增加了代码的缩进级别,扩展性和可读性都很差,且万一忘记判断某个变量,依然出现NPE。

例2

public String getCarInsuranceName(Person person){
    if(person == null){
        return "unknown";
    }
    Car car = person.getCar();
    if(car == null){
        return "unknown";
    }
    Insurance insurance = car.getInsurance();
    if(insurance == null){
        return "unknown";
    }
    return insurance.getName();
}

这种方式避免深层嵌套的if块,但是每一个null检查点都增加一个退出点,难以维护,且为null时,返回的字符串“unknown”重复出现。同样的,当忘记检查某个可能为null的属性时,会出现NPE。

 

2、null带来的问题

NPE是目前Java程序开发种最典型的异常;

会使代码膨胀,深度嵌套的null检查,代码的可读性差;

null自身是毫无意义的,null没有任何语义;

破坏了Java的哲学,Java一直试图避免让程序员意识到指针,唯一的例外就是null指针;

null在Java的类型系统上成了例外,null不属于任何类型,即它可以赋值给任意引用类型,当这个变量被传递到系统的另一个部分后,将无法获知这个null变量最初的赋值到底什么类型。

 

3、其他语言中null的处理(替代)

Groovy:安全导航操作符(safe navigation operator, 标记为?)

  e.g. def carInsuranceName = person?.car?.insurance?.name 当调用链中的变量遇到null时将null引用沿着调用链传递下去,返回一个null。

函数式语言:

   Haskell:Maybe类型,其本质上是对optional值的封装。Maybe类型的变量可以是指定类型的值,也可以什么都不是。没有null引用的概念。

   Scala:Option[T],既可以包含类型为T的变量,也可以不包含该变量,显式调用Option类型的available操作,检查该变量是否有值,即变相的“null”检查。

 

4、Java8的Optional类

Java8引入Optional类,在从null到Optional的迁移中,需要反思的是:如何在你的域模型中使用Optional值。

 

我们根据上面例子的类关系可知,Person的Car变量存在null情况,因此不能直接声明为Car,改为Optional<Car>。

 

4.1 这样做有什么好处呢?

       使用Optional<Car>声明变量,清楚的表明了这里的变量缺失是允许的,而使用Car类型,可能将变量赋值为null,就只能依赖业务模型的理解,判断一个null是否属于该变量的有效范畴。

我们根据Optioanl类重新定义Person/Car/Insurance的数据模型:

public class Person{
    private Optional<Car> car;
    public Optional<Car> getCar() {return car;}
}    
public class Car{
    private Optional<Insurance> insurance;
    public Optional<Insurance> getInsurance(){return insurance;}
}
public class Insurance{
    private String name;  // 保险公司必须有名字
    public String getName(){return name;}
}

当获取insurance公司名称发生NPE,就能非常确定出错的原因,不需要为其添加null的检查,因为null的检查只会掩盖问题,并未真正地修复问题。insurance公司必须有名字,当公司没有名称,就需要查看出了什么问题,而不应该再添加一段代码,将这个问题隐藏。

 

4.2 引入Optional类的目的

       代码中始终如一使用Optional,能非常清晰地节点出变量值的缺失是结构上的问题还是算法上或是数据中的问题。

       引入Optional并不是要消除每一个null引用,而是帮助设计更普适的API,看见签名就能了解是否接受一个Optional的值,这种强制会让你更积极地将变量从Optional中解包出来,直面缺失的变量值。

       即看见Optional<Car>就可以知道该变量car可能为null。

 

4.3 null与Optional.empty()

       尝试解引用一个null,一定会触发NPE,而使用Optional.empty()就没事,其为Optioanl类的一个有效对象,多种场景都能调用,非常有用。

Optional.empty()的定义如下

/**
 * Returns an empty {@code Optional} instance.  No value is present for this
 * Optional.
 *
 * @apiNote Though it may be tempting to do so, avoid testing if an object
 * is empty by comparing with {@code ==} against instances returned by
 * {@code Option.empty()}. There is no guarantee that it is a singleton.
 * Instead, use {@link #isPresent()}.
 *
 * @param <T> Type of the non-existent value
 * @return an empty {@code Optional}
 */
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

EMPTY的定义如下:

/**
 * Common instance for {@code empty()}.
 */
private static final Optional<?> EMPTY = new Optional<>();

 

4.4 使用Optional

(1) 使用map从Optional对象中提取和转换值

// 原先写法
String name = null;
if (insurance != null){
  name = insurance.getName();
}

// 使用Optional
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

stream和map方法:

 

 

(2)使用flatMap方法连接Optional对象

// 原先写法
public String getCarInsuranceName(Person person){
    return person.getCar().getInsurance().getName();
}

// 使用Optional.map会报错
/*因为Person的类型是Optional<Computer>,调用map方法是正常的,
但是,getCar()方法返回的是一个Optional<Car>对象,
即这个map操作后得到的类型是Optional<Optional<Car>>.
对一个Optional对象调用getInsurance ()是非法的。
*/ public String getCarInsuranceName(Optional<Person> person){ return person.map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName) .orElse("UNKNOWN"); } // 使用Optional.flatMap public String getCarInsuranceName(Optional<Person> person){ return person.flatMap (Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("UNKNOWN"); }

stream和flatMap方法比较:

 

 

flatMap方法连接Optional对象过程:

 

 

(3)map和flatMap方法源码

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.ofNullable()会返回一个Optional<>
    }
} 

//如果调用的mapper返回已经是Optional,则调用该mapper后,flatMap不会再添加Optional
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));  //返回参数的类型
    }
}

Objects类的requireNonNull方法:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

 

4.5 使用Optional域,该域无法序列化

       在域模型中使用Optional,该域无法序列化,因为Optional没有实现Serializable接口。

       Java语言的架构师Brain Goetz明确陈述过:Optional的设计初衷仅仅是要支持能返回Optional对象的语法。

       Optional类设计时就没有特别考虑将其作为类的字段使用,所以它并未实现Serializable接口。

若一定要序列化域,替代方案:

public class Person{
    private Car car;
    public Optional<Car> getCarOptional(){
        return Optional.ofNullable(car);
    }
}

4.6 应用

(1)用Optional封装可能为null的值

Object value = map.get("key");
↓↓
Optional<Object> value = Optional.ofNullable(map.get("key"));

(2)异常

Integer.parseInt(String) --> NumberFormatException
↓↓
public static Optional<Integer> stringToInt(String s){
    try{
        return Optional.of(Integer.parseInt(s));
    } catch(NumberFormatException e){
        return Optional.empty();
    }
}

(3)基础类型的Optonal对象

        Optional也提供了基础类型--OptionalInt, OptionalLong, OptionalDouble,但是不推荐使用,因为基础类型的Optional不支持map、flatMap以及filter方法。

(4)整合

Properties props = new Properties();
props.setProperty("a", "5");
props.setProperty("b", "true");
props.setProperty("c", "-3");

需求:从这些属性中读取一个值,该值的含义是时间,单位s,所以必须≥0.

public int readDuration(Properties props, String name){
    String value = props.getProperty(name);
    if (value != null){
        try{
            int i = Integer.parseInt(value);
            if (i > 0){
                return i;
            }
        }catch(NumberFormatException nfe){}
    }
    return 0;
}

↓↓

public int readDuration(Properties props, String name){
    return Optional.ifNullable(props.getProperty(name))
                   .flatMap(OptionalUtil::stringToInt)   // ②中的方法
                   .filter(i -> i > 0)
                   .orElse(0);
}

 

参考文献:《Java8实战》(英)Urma, R.G. (意)Fusco, M. (英)Mycroft, A,著;陆明刚 劳佳译. 北京:人民邮电出版社,2016.4

                 官网:https://www.oracle.com/technical-resources/articles/java/java8-optional.html

posted @ 2020-10-17 14:28  dedication  阅读(1649)  评论(0编辑  收藏  举报