Java-8用Option代替Null

27次阅读

共计 3084 个字符,预计需要花费 8 分钟才能阅读完成。

可怕的 NullPointerException

NPE : NullPointerException

空指针异常是最常见的 Java 异常之一,抛出 NPE 错误不是用户操作的错误,而是开发人员的错误,应该被避免,那么只能在每个方法中加入非空检查,阅读性和维护性都比较差。

以下是一个常见的嵌套对象:一个用户所拥有的汽车,以及为这个汽车配备的保险。

public class User {

    private String userName;

    private Car car;

    public String getUserName() {return userName;}

    public void setUserName(String userName) {this.userName = userName;}

    public Car getCar() {return car;}

    public void setCar(Car car) {this.car = car;}
}

public class Car {

    private String carName;

    private Insurance insurance;

    public String getCarName() {return carName;}

    public void setCarName(String carName) {this.carName = carName;}

    public Insurance getInsurance() {return insurance;}

    public void setInsurance(Insurance insurance) {this.insurance = insurance;}
}

public class Insurance {

    private String insuranceName;

    public String getInsuranceName() {return insuranceName;}

    public void setInsuranceName(String insuranceName) {this.insuranceName = insuranceName;}
}

如果我们此时,需要获取一个用户对应的汽车保险名称,我们可能会写出来以下的代码

private String getInsuranceName(User user) {return user.getCar().getInsurance().getInsuranceName();
}

显然上面的程序是存在诸多 NullPointerException 隐患的,为了保证程序的健壮性,我们需要尽量避免出现空指针 NullPointerException,那么通常我们会有以下两种写法。

深层质疑

private String getInsuranceName(User user) {if (user != null) {Car car = user.getCar();
            if (car != null) {Insurance insurance = car.getInsurance();
                if (insurance != null) {return insurance.getInsuranceName();
                }
            }
        }
        return "not found";
    }

及时退出

    private String getInsuranceName(User user) {if (user == null) {return "not found";}
        Car car = user.getCar();
        if (car == null) {return "not found";}
        Insurance insurance = car.getInsurance();
        if (insurance == null) {return "not found";}
        return insurance.getInsuranceName();}

为了避免出现空指针,我们通常会采用以上两种写法,但是它们复杂又冗余,为了鼓励程序员写更干净的代码,代码设计变得更加的优雅。JAVA8 提供了 Optional 类来优化这种写法。

Option 入门

Java 8 中引入了一个新的类 java.util.Optional<T>。这是一个封装 Optional 值的类。举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有车,那么 Person 类内部的 car 变量就不应该声明为 Car,遇某人没有车时把 null 引用值给它,而是应该如下图所示直接将其声明为 Optional<Car> 类型。

变量存在时,Optional 类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的 Optional 对象,由方法 Optional.empty() 返回。它返回 Optional 类的特定单一实例。

null 引用和 Optional.empty() 有什么本质的区别吗?

从语义上,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大: 如果你尝试直接引用一个 null,一定会触发 NullPointerException,不过使用 Optional.empty() 就完全没事儿,它是 Optional 类的一个有效对象。

使用 Optional 而不是 null 的一个非常重要而又实际的语义区别是,第一个例子中,我们在声明变量时使用的是 Optional<Car> 类型,而不是 Car 类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。与此相反,使用 Car 这样的类型,可能将变量赋值为 null,你只能依赖你对业务模型的理解,判断一个 null 是否属于该变量的有效值又或是异常情况。

public class User {

    private String userName;

    private Optional<Car> car;

    public String getUserName() {return userName;}
    public Optional<Car> getCar() {return car;}
}
public class Car {
    private String carName;
    private Optional<Insurance> insurance;
    public String getCarName() {return carName;}
    public Optional<Insurance> getInsurance() {return insurance;}
}
public class Insurance {
    private String insuranceName;
    public String getInsuranceName() {return insuranceName;}
}

发现 Optional 是如何 富你模型的语义了吧。代码中 person 引用的是 Optional<Car>,而 car 引用的是 Optional<Insurance>,这种方式非常清晰地表达了你的模型中一个 person 可能有也可能没有 car 的情形,同样,car 可能进行了保险,也可能没有保险。

与此同时,我们看到 insurance 的名称 insuranceName 被声明成 String 类型,而不是 Optional- <String>,这非常清楚地表明声明为 insurance 的类中的名称字段 insuranceName 是必须存在的。

使用这种方式,一旦通过引用 insurance 获取 insuranceName 时发生 NullPointerException,你就能非常确定地知道出错的原因,不再需要为其添加 null 的检查查,因为 null 的检查查只会掩盖问题,并未真正地修复问题。

insurance 必须有个名字,所以,如果你遇到一个 insurance 没有名称,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。

正文完
 0