Java8新特性Optional

Java8新特性Optional

避之不及的NullPointerException
NPE : NullPointerException
  1. 空指针异常是最常见的Java异常之一,抛出的错误不是用户操作的错误,而是开发人员的错误,应该可以避免,那么只能在每个方法中加入非空检查,阅读性的维护性都比较差。
    以下是一个常见的嵌套对象:一个用户拥有汽车,以及为这个汽车配备的保险。
@Data
public class User {
    private String userName;
    private Car car;
}
@Data
public class Car {
    private String carName;
    private Insurance insurance;
}
@Data
public class Insurance {
    private String insuranceName;
}
  1. 如果此时,我们需要获取一个用户对应的汽车保险名称,我们可能会写出来以下代码
private String getInsuranceName(User user) {
    return user.getCar().getInsurance().getInsuranceName();
}
  1. 显然上面的程序是存在诸多的NullPointerException隐患的,为了保证程序的健壮性,我们需要尽量的避免出现空指针异常,那么通常会有以下两种写法。

    1. 深层质疑
    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";
    }
    
    1. 及时退出
    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类来处理这种写法。

  2. Optional的入门

    • Java8中引入了一个新的类java.util.Optional,这是一个封装Optional值的类,举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有车,那么user类内部的car变量就不应该声明为car,遇到某人没有车时把null引用值给它后就有可能会出现空指针的问题,应该如下图所示直接将其声明为Optional类型。在这里插入图片描述

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

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

      使用Optional而不是null的一个非常重要而又实际的语义区别是,第一个例子中,我们在声明变量时使用的是Optional类型,而不是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,这种方式非常清晰地表达了你的模型中一个user可能有也可能没有car的情形,同样,car可能进行了保险,也可能没有保险。

      与此同时,我们看到insurance的名称insuranceName被声明成String类型,而不是Optional,这非常清楚地表明声明为insurance的类中的名称字段insuranceName是必须存在的。所以,如果你遇到一个insurance没有名称,出现空指针异常的时候,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。

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

  3. 创建Optional
    of(T value)
    如果构造参数是一个null,这段代码会立即抛出一个NullPointerException,而不是等到你试图访问car属性值时才返回个错误。

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
    

    ofNullable(T value)
    创建一个允许null值的Optional对象

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
    

    empty()
    创建一个新的Optional对象

    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    

    常用方法

    • get()
      是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional变量一定包含值,否则最好不要使用这个方法。
      public T get() {
          if (value == null) {
              throw new NoSuchElementException("No value present");
          }
          return value;
      }
      
    • orElse(T other)
      它允许你在Optional对象不包含值时提供一个默认值。如果T有值时接着走这个方法。传入的值必须的T类型相同。
      public T orElse(T other) {
      	return value != null ? value : other;
      }
      
    • orElseGet(Supplier<? extends T> other)
      是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。如果T有值时不走这个方法。Lambda表达式。
      public T orElseGet(Supplier<? extends T> other) {
      	return value != null ? value : other.get();
      }
      
      注意:
      orElse(T other)和orElseGet(Supplier<? extends T> other)的区别

      当value值不为null时,orElse函数依然会执行返回T的方法,而orElseGet函数并不会执行返回T的方法。
    • orElseThrow(Supplier<? extends X> exceptionSupplier),和get方法类似,它们遇到Optional对象为空时都会抛出一个异常,但是orElseThrow指你可以自定义异常类型。Lambda表达式。
      public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
          if (value != null) {
              return value;
          } else {
              throw exceptionSupplier.get();
          }
      }
      
    • ifPresent(Consumer<? super T>),让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
      public void ifPresent(Consumer<? super T> consumer) {
          if (value != null)
              consumer.accept(value);
      }
      
    1. isPresent()
      来判断当前Optional中的值是否不为空。不为空返回true,反之。

      public boolean isPresent() {
      	return value != null;
      }
      
    2. 用map从Optional中提取和转换值
      可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。

      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));
          }
      }
      
      String optionMap = Optional.ofNullable("abc").map(value -> value.toUpperCase()).get();
      
    3. 使用flatMap链接Optional对象
      将两层的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));
          }
      }
      
      String optionFlatMap = Optional.ofNullable("abc").flatMap(value -> Optional.of((value + "flat-map").toUpperCase())).get();
      
    4. 用filter剔除特定的值
      filter(Predicate<? super T> predicate)
      filter方法接受一个谓词作为参数。如果Optional对象的值存在,并且它符合谓词的条件, filter方法就返回其值;否则它就返回一个空的Optional对象。

      public Optional<T> filter(Predicate<? super T> predicate) {
      	Objects.requireNonNull(predicate);
          if (!isPresent())
              return this;
          else
              return predicate.test(value) ? this : empty();
      }
      
      Optional<String> filterOptional = Optional.ofNullable("abc").filter(value -> Objects.equals(value, "abc"));
      
  4. 避免使用Optional.isPresent()来检查实例是否存在,因为这种方式和null != obj没有区别,这样用就没什么意义了。

    避免使用Optional.get()方式来获取实例对象,因为使用前需要使用Optional.isPresent()来检查实例是否存在,否则会出现NoSuchElementException异常问题。所以使用orElse(),orElseGet(),orElseThrow()获得你的结果

    用了 isPresent() 处理 NullPointerException 不叫优雅, 有了 orElse, orElseGet 等, 特别是 map 方法才叫优雅

    其他几个, filter() 把不符合条件的值变为 empty(), flatMap() 总是与 map() 方法成对的, orElseThrow() 在有值时直接返回, 无值时抛出想要的异常.

    一句话小结: 使用 Optional 时尽量不直接调用 Optional.get() 方法, Optional.isPresent() 更应该被视为一个私有方法, 应依赖于其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法.

  5. 实战场景

    1. 默认值

      传统方式

      public static String getName(User u) {
          if (u == null)
              return "Unknown";
          return u.name;
      }
      

      杜绝使用这种方式(不简洁)

      public static String getName(User u) {
         Optional<User> user = Optional.ofNullable(u);
          if (!user.isPresent())
              return "Unknown";
          return user.get().name;
      }
      

      正确方式(链式调用):

      public static String getName(User u) {
      	return Optional.ofNullable(u)
                      .map(user->user.name)
                      .orElse("Unknown");
      				//.orElseGet(() -> "john");
      }
      
    2. 多重非空条件判断

      传统方式

      public static String getChampionName(Competition comp) throws IllegalArgumentException {
          if (comp != null) {
              CompResult result = comp.getResult();
              if (result != null) {
                  User champion = result.getChampion();
                  if (champion != null) {
                      return champion.getName();
                  }
              }
          }
          throw new IllegalArgumentException("The value of param comp isn't available.");
      }
      

      链式调用(map 遍历属性)

      public static String getChampionName(Competition comp) throws IllegalArgumentException {
          return Optional.ofNullable(comp)
                  .map(c->c.getResult())
                  .map(r->r.getChampion())
                  .map(u->u.getName())
                  .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
      }
      
    3. 不为空才操作(单边判断)

      string.ifPresent(System.out::println);
      
    4. 指定条件过滤

      public boolean priceIsInRange2(Modem modem2) {
           return Optional.ofNullable(modem2)
             .map(Modem::getPrice)
             .filter(p -> p >= 10)
             .isPresent();
       }
      
    5. filter 与 findFirst 结合

      Optional<String> found = Stream.of(getEmpty(), getHello(), getBye())
        .filter(Optional::isPresent)
        .map(Optional::get)
        .findFirst();
      
  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值