Optional的概念
java.util.Optional 是java8中引进的一个新的类,它可以对可能缺失的值进行建模,而不是直接将null赋值给变量。
它是用来规范我们开发的API,使其语义更加的明确,使用Optional修饰的对象,表示该对象可能为null。在一定程度上避免了空指针的问题。
Optional的由来
身为java程序员,空指针是最常见的问题了,它是在1965年被一位英国的计算机科学家 Tony Hoare开放的,他设计的初衷是因为采用null的这种引用方式,实现起来非常的容易。但是后来让众多开发人员痛苦不已,这也让他十分的后悔,自称为“价值百万的重大失误”。
每当我们对对象的格式进行检查,判断它的值是否是期望的格式时,却发现我们看到的并不是一个对象,而是一个空指针(java中没有指针,其实就是引用),这时就会抛出NullPointerExceptione的异常。
下面举个列子来验证该问题。
public class Person {
private Car car;
public Car getCar(){
return car;
}
}
class Car{
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
}
class Insurance{
private String name;
public String getName() {
return name;
}
}
以上是要测试的三个类,现在要写一个方法,通过人员来获取保险
public String getCarInsuranceName(Person person){
return person.getCar().getInsurance().getName();
}
上面的代码就会造成空指针的问题,因为有的人没有车,并且有的车没有保险。为了解决空指针的问题,我们常用的方法是防御式编程,可以进行如下的操作:
public 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的检查,这样看似可以避免了空指针,却十分的冗余,因为当不确定一个变量是否为null时,都需要进行if的检查,这不仅增加了代码的可读性,还容易漏掉,并且维护起来也比较困难。
java引入空指针的危害:
1、它是很多问题的错误之源,它是目前开发中最典型的异常。
2、它会使代码膨胀,它会使我们的代码充满了深度嵌套的null检查,代码的可读性下降。
3、它自身是毫无意义的,null自身没有任何的语义,它表示以一种错误的方式对缺失变量的值建模。
4、它破坏了java的哲学,java一直避免引入指针的存在,而唯一的例外就是null指针。
5、它破坏了java的类型,null不属于任何类型,这也意味着它可以被赋值给任意引用类型的变量,我们将无法获取这个null值最初的类型是什么。
而Optional的出现可以让我们最大程度上规避上述问题。
Optional的使用
Optional入门教程
拿最上面的例子来讲,如果我们知道有的人没有车,那就不应该在Personal类内部声明Car的变量,因为Car类型存在,就说明一定会有Car类型的变量,而事实上并不是这样,所以使用Car类型不是一个明智的选择。我们可以使用Optional类对其进行包裹,当存在Car类型变量的时候就返回,当不存在的时候就返回一个空的Optional对象,它就像一个盒子,修饰的对象被放了进去。
除此之外,最重要的是,这就变的非常的明确,用Optional修饰,说明这里是允许变量缺失的。
public class Person {
//有的人有车,也可能没有车,所以这里用Optional来修饰
private Optional car;
public Optional getCar(){
return car;
}
public void setCar(Optional car) {
this.car = car;
}
}
class Car{
//有的车上了保险,也有可能没有上,所以这里使用Optional修饰
private Optional insurance;
public Optional getInsurance() {
return insurance;
}
public void setInsurance(Optional insurance) {
this.insurance = insurance;
}
}
class Insurance{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
当我们再次声明方法的时候,可以按如下方式操作:
public class OptionalTest {
public static void main(String[] args) {
Person person = new Person();
Car car = new Car();
Insurance insurance = new Insurance();
//Optional.of()表示对象不能为null
insurance.setName("insurance");
car.setInsurance(Optional.of(insurance));
person.setCar(Optional.of(car));
String carInsuranceName = getCarInsuranceName(person);
}
public static String getCarInsuranceName(Person person) {
//可以通过get方法从Optional中取出值
return person.getCar().get().getInsurance().get().getName();
}
}
这样我们就不需要进行null的判断、检查,因为发生异常的时候,会直接在赋值为null的地方进行报错,而不会在调用方法的时候出现空指针。
需要注意的是:
Optional只是消除了进行null检查的逻辑,并快速定位问题,而不是消除每一个null的引用,相反,它的目标是帮助我们设置更优秀的API,让开发人员看到签名就知道这个变量的含义。
看到这里,有的同学觉得Optional只是形式上声明吗?不能规避空指针吗?答案是可以的,
创建Optional对象
创建Optional的方式有多种
1、声明一个空的Optional
//使用Optional.empty()方法创建一个空的Car类型的Optional对象。
Optional optionalCar = Optional.empty();
2、创建一个非空值的Optional,如果car为null的话,直接抛出空指针异常(参考上面的图片)。
Car car = new Car();
Optional car1 = Optional.of(car);
3、创建一个可以为null的Optional,该方法支持car为null,但是会在用到car的地方抛出异常,但不是空指针异常。
Car car = new Car();
Optional car2 = Optional.ofNullable(car);
从Optional对象中提取和转换值
虽然我们可以通过get方法从Optional中取出值,但是get方法在遭遇到空的Optional对象时仍然会抛出空指针异常,并没有解决我们的问题。所以我们需要按照约定的方式来使用它。
使用map从Optional对象中提取和转换值
就像stream流一样,这里也是可以通过通过map的方式从Optional中取出元素,map方法返回的也是一个Optional类型的对象,里面的值如果为null的话,可以使用orElse方法赋上自定义的值。
Insurance insurance = new Insurance();
insurance.setName("insurance");
Optional optionalInsurance = Optional.ofNullable(insurance);
String name = optionalInsurance.map(Insurance::getName).orElse("unKnown");
使用flatMap链接Optional对象
如果想要获取下面最终的值,我们通过map方法来尝试一下
public String getCarInsuranceName(Person person){
return person.getCar().getInsurance().getName();
}
当使用map的时候,代码如下:
public static String getCarInsuranceName(Person person) {
Optional optionalPerson = Optional.of(person);
return optionalPerson.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("unKnown");
}
如上代码是编译不通过的,这里将person进行了Optional来修饰,才可以使用map方法,然后Person里面的Car对象也是用Optional来修饰的,所以使用map方法取出来的是一个用Optional
public static String getCarInsuranceName(Person person) {
Optional optionalPerson = Optional.of(person);
return optionalPerson.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("unKnown");
}
为了更清晰的展示map和flatMap的区别,大家可通过下面的demo来理解
Person person = new Person();
Optional optionalPerson = Optional.of(person);
//map方法
Optional> car1 = optionalPerson.map(Person::getCar);
//flatMap方法
Optional optionalCar1 = optionalPerson.flatMap(Person::getCar);
这里还需要注意的是:
如果对象没有使用Optional来修饰,直接使用get方法就可以拿到对象里面的Optional的值
如果对象使用Opional来修饰的话,就需要使用map方法。
Optional默认行为
get()
get()方法是最简单也是最不安全的方法,如果变量存在就返回,不存在的话则会抛出NoSuchElementException的异常。所以,get()的使用场景一定是十分确定Optional修饰的值一定是有内容的,否则不建议使用。使用的demo:
Car car = new Car();
Insurance insurance = new Insurance();
car.setInsurance(Optional.of(insurance));
//使用get来从Optional中取值。
Insurance insurance1 = car.getInsurance().get();
orElse()
该方法相对于get()的好处在于当Optional对象中不存在则可以返回一个默认的值,使用的demo:
Car car = new Car();
Insurance insurance = new Insurance();
car.setInsurance(Optional.of(insurance));
//使用orElse来赋予默认的值
Insurance insurance1 = car.getInsurance().orElse(new Insurance());
orElseGet()
该方法是orElse()方法的延迟调用版,当对象为空的时候才会产生默认值,它的性能相对于orElse()来说更好一些,建议使用该方法,下面是orElseGet()和orElse()的源码对比:
package java.util;
public final class Optional {
...
public T orElse(T other) {
return this.value != null ? this.value : other;
}
public T orElseGet(Supplier extends T> supplier) {
return this.value != null ? this.value : supplier.get();
}
}
orElseThrow()
该方法和get方法很类似,当Optional修饰的对象为空的时候来抛出一个自己指定的异常类型。
ifPresent()
该方法对Optional修饰的对象进行判断,如果存在对象,则在进行某些操作,该方法里面放的是一个Consumer函数式接口,入参为T,void的类型的方法。
public void ifPresent(Consumer super T> consumer) {
if (value != null)
consumer.accept(value);
}
使用demo:
person.getCar().ifPresent(System.out::println);