避之不及的 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类来优化这种写法。

Optional

Java 8中引入了一个新的类java.util.Optional<T>。这是一个封装Optional值的类。举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有车,那么User类内部的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是如何 富你模型的语义了吧。代码中user引用的是Optional<Car>, 而car引用的是Optional<Insurance>,这种方式非常清晰地表达了你的模型中一个user 可能有也可能没有car的情形,同样,car可能进行了保险,也可能没有保险。

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

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

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

Optional的方法介绍

创建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() {
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

常用方法

  • get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional变量一定包含值,否则最好不要使用这个方法。
  • orElse(T other),它允许你在 Optional对象不包含值时提供一个默认值。
  • orElseGet(Supplier<? extends T> other)是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。
  • orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法非常类似,它们遭遇Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希望抛出的异常类型。
  • ifPresent(Consumer<? super T>)让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

注意:orElse(T other)和orElseGet(Supplier<? extends T> other)的区别

这两个函数的区别:当value值不为null时,orElse函数依然会执行返回T的方法,而orElseGet函数并不会执行返回T的方法。

用map从Optional中提取和转换值

map(Function<? super T, ? extends U> mapper)

可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。

String optionMap = Optional.ofNullable("abc").map(value -> value.toUpperCase()).get();

使用flatMap链接Optional对象

flatMap(Function<? super T, Optional<U>> mapper)

将两层的optional合并为一个

String optionFlatMap = Optional.ofNullable("abc").flatMap(value -> Optional.of((value + "flat-map").toUpperCase())).get();

用filter剔除特定的值

filter(Predicate<? super T> predicate)

filter方法接受一个谓词作为参数。如果Optional对象的值存在,并且它符合谓词的条件, filter方法就返回其值;否则它就返回一个空的Optional对象。

Optional<String> filterOptional = Optional.ofNullable("abc").filter(value -> Objects.equals(value, "abc"));

实战

尝试获取用户的用户名称,不存在则返回默认值

String userName = Optional.ofNullable(userOfNull).orElse(new User()).getUserName();

尝试获取用户的carName,不存在则返回null

String carName = Optional.ofNullable(userOfNull).map(u -> u.getCar()).map(c -> c.getCarName()).orElse(null);

用户名存在的时候转为大写

Optional.ofNullable(user).map(u -> u.getUserName()).ifPresent(userName -> System.out.println(userName.toUpperCase()));

过滤出来用户名称是张三的用户

Optional.ofNullable(user).filter(u -> Objects.equals(u.getUserName(),"张三")).map(u -> u.getUserName()).ifPresent(userName -> System.out.println(userName + "实战Test"));

将张三的用户名称更改为李四

Optional.ofNullable(user).ifPresent(x -> {
if (Objects.equals(user.getUserName(),"张三")){
user.setUserName("李四");
}
}); Optional.ofNullable(user).filter(u -> Objects.equals(user.getUserName(),"张三")).ifPresent(x -> user.setUserName("李四"));  

【Java 8】巧用Optional之优雅规避NPE问题的更多相关文章

  1. 如何优雅规避NPE

    项目中尤其是在持久层,难免会有大量的针对集合,对象,字符串的操作,为了程序的健壮性,我们不得不进行判空,像下面箭头式编码吗? if(null!=person){ ... if(null!=addres ...

  2. Java 8 Optional:优雅地避免 NPE

    本篇文章将详细介绍 Optional 类,以及如何用它消除代码中的 null 检查.在开始之前首先来看下什么是 NPE,以及在 Java 8 之前是如何处理 NPE 问题的. 空指针异常(NullPo ...

  3. 浅析Java 8新功能Optional

    初识 A container object which may or may not contain a non-null value. 笔者理解,Optional是一个容器类.将Object放到Op ...

  4. 使用Optional,不再头疼NPE

    前言 在 Java 语言开发中,可能大多数程序员遇到最多的异常就是 NullPointException 空指针异常了.这个当初语言的开发者"仅仅因为这样实现起来更容易"而允许空引 ...

  5. Java枚举:小小enum,优雅而干净

    <Java编程思想>中有这么一句话:“有时恰恰因为它,你才能够‘优雅而干净’地解决问题”——这句话说的是谁呢?就是本篇的主角——枚举(Enum)——大家鼓掌了. 在之前很长时间一段时间里, ...

  6. java~lambda表达式让查询更优雅

    在java之前的版本里,如果希望从集合时查找符合条件的数据,如果先遍历他,这种写法是我们不能接受的,所以现在java有了lambda就很好的解决了这个问题,让代码更优雅一些! /** * lambda ...

  7. java 日志脱敏框架 sensitive,优雅的打印脱敏日志

    问题 为了保证用户的信息安全,敏感信息需要脱敏. 项目开发过程中,每次处理敏感信息的日志问题感觉很麻烦,大部分都是用工具类单独处理,不利于以后统一管理,很不优雅. 于是,就写了一个基于 java 注解 ...

  8. Java进阶知识点3:更优雅地关闭资源 - try-with-resource及其异常抑制

    一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...

  9. Java 8 新特性-Stream更优雅的处理集合入门

    Java 8 新特性之--Stream 一. 简单介绍 Stream是Java 8提出了的一种新的对集合对象功能的增强.它集合Lambda表达式,对集合提供了一些非常便利,高效的操作,使得代码具有非常 ...

随机推荐

  1. JS中的七大数据类型

    js中有7种数据类型,包括五种基本数据类型(Number,String,Boolean,Undefined,Null),和一种复杂数据类型(Object)以及es6语法新增的Symbol数据类型 es ...

  2. springboot2.1.3使用mvn site遇到的坑及解决方案

    本人要使用mvn site命令生成一些项目报告,如:***.html文件,但是在命令运行时,时常报如下错误: 一开始还以为是 jar包冲突引起的,把相关依赖引用的jackson-module-scal ...

  3. Notepad++常用快捷键:

    Ctrl-H 打开Find / Replace 对话框 Ctrl-D 复制当前行 Ctrl-L 删除当前行 Ctrl-T 上下行交换 F3 找下一个 Shift-F3 找上一个 Ctrl-Shift- ...

  4. c风格的字符串

    c风格的字符串的标注库 #include <cstring> 使用c 风格的字符串,牢记,其必须以null为结束标志 如 char ca[]={'c','+','='}; cout< ...

  5. IntelliJ IDEA 2019.2破解

    IntelliJ IDEA 2019.2破解 我是参考这个激活的,使用的激活码的方式,需要在百度云盘下载压缩包 https://zhile.io/2018/08/25/jetbrains-licens ...

  6. JQuery系列(3) - 工具方法

    jQuery函数库提供了一个jQuery对象(简写为$),这个对象本身是一个构造函数,可以用来生成jQuery对象的实例.有了实例以后,就可以调用许多针对实例的方法,它们定义jQuery.protot ...

  7. php+tcpdf生成pdf: 中文乱码

    TCPDF是一个生成PDF的不错的库,可惜,官方对包括中文在内的东亚字体支持不怎么样的.场景:某项目需要根据数据库信息生成pdf格式的发票,考虑采用稳定的tcpdf,虽然还有许多其它选择,但是这个应该 ...

  8. Java web开发——文件夹的上传和下载

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 这次项目的需求: 支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,i ...

  9. Don't rely on luck.

    https://www.codewars.com/kata/dont-rely-on-luck/train/javascript 答案: 重写 Math.floor = function () ... ...

  10. Linux下搭建iSCSI共享存储的方法 Linux-IO Target 方式CentOS7-1810下实现

    iSCSI(internet SCSI)技术由IBM公司研究开发,是一个供硬件设备使用的.可以在IP协议的上层运行的SCSI指令集,这种指令集合可以实现在IP网络上运行SCSI协议,使其能够在诸如高速 ...