• Java优雅解决空指针问题源码级别刨析Optional



    1 Optional介绍

    在日常开发中,NullPointerException相信所有人都见过,不管你是刚入行的萌新还是骨灰级玩家,对于它都是耳熟能详的。它的出现可以说无处不在,总是能在各种场景下出现。那么对于如何防止它的出现,我们平时都是被动的采用各种非空校验,但是它还是经常能出现在我们的视线中。

    public String getCompanyName(Student
    student){
      if (student != null){
        Job job = student.getJob();
        if (job != null){
          Company company =
    job.getCompany();
          if (company != null){
            String name =
    company.getName();
            return name;
         }else {
            return "no company";
         }
       }else {
          return "no job";
       }
     }else {
        return "no student";
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    对于上述这段代码,相信大家平时工作类似的代码经常会有出现。每一次在获取到一个对象时都进行一个null的判断,然后才继续后续的实现。但是这种方式很不好,首先会存在大量的if-else判断嵌套,导致代码的可读性和扩展性极差。此时,有的同学可能就会这么改造,如下所示:

    public String getCompanyName(Student
    student){
      if (student == null){
        return "no student";
     }
      Job job = student.getJob();
      if (job == null){
        return "no job";
     }
      Company company = job.getCompany();
      if (company == null){
        return "no company";
     }
      return company.getName();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这种判断已经有意识的避免了大量嵌套判断,但是同样存在多个不同的判断点,代码维护同样困难。
    那么有没有一种方式可以优雅的解决这些问题呢?

    2 Optional应用

    为了防止空指针异常的出现,Java8中引入了一个新类Optional,对于它之前我们已经进行了简单的实现。其本质就是通过Optional类对值进行封装, 当有值的时候,会把该值封装到Optional类中。如果没有值的话,则会在该类封装一个Empty

    在Optional类中基于函数式接口提供了一些用于操作值的方法。
    在这里插入图片描述

    2.1 创建Optional对象

    要创建Optional,该类提供了三种方法操作,分别为:
    empty()、of()、ofNullable()。使用方式如下所示:

    Optional<Student> studentOptional =Optional.empty();
    Optional<Student> studentOptional =Optional.of(student);
    Optional<Student> studentOptional =Optional.ofNullable(student);
    
    • 1
    • 2
    • 3

    可以看到这三个方法,都会返回Optional对象,那么三者之间有什么区别呢?根据源码分析如下:
    在这里插入图片描述
    根据源码可知,empty()会直接返回一个空的Optional实例,内部不会存在任何值。
    在这里插入图片描述
    根据源码可知,of()会返回一个存在值的Optional对象,并且该值不允许null的存在。如果调用该方法时传入参数是null,则立刻抛出NullPointerException,而不是等到你用这个对象时才抛出,相当于进行了立即检查。
    在这里插入图片描述
    根据源码可知,ofNullable()同样也会返回一个存在值的Optional对象,但是它和of()最大的不同在于,它会对传入的值进行判断,如果传入的值为null,其会调用empty()返回一个不包含内容的Optional,如果不为null,则会调用of()返回一个包含内容的Optional

    2.2 基于Optional对象获取值

    有了Optional对象之后,就需求获取其内部的值了,Optional类也提供了多种方法用于值的获取。

    2.2.1 isPresent()与ifPresent()应用&源码解析

    Optional类中提供了两个方法用于判断Optional是否有值,分别是isPresent()和ifPresent(Consumerconsumer)。其一般与ofNullable()搭配使用,因为of()在创建时已经完成了判断,而empty()只是单纯了实例化了一个Optional对象。在这里插入图片描述
    根据源码可知,isPresent()内部非常简单,就是判断这个值是否为null。
    在这里插入图片描述
    根据源码可知,该方法在执行时,接收一个consumer函数式接口,如果value不为null,则通过consumer中的accept方法获取该值。
    使用示例如下:

    public class PresentDemo {
      public static void
    getStudentName(Student student){
        Optional<Student> studentOptional =
    Optional.ofNullable(student);
        if (studentOptional.isPresent()){
          //存在
          System.out.println("student存
    在");
      }else {
          System.out.println("student不存
    在");
       }
     }
      public static void main(String[] args) {
        Student student = new
    Student(1,"zhangsan","M");
        getStudentName(student);
     }
    }
    Optional<Student> studentOptional =
    Optional.ofNullable(student);
    studentOptional.ifPresent(s->
    System.out.println("学生存在"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.2.2 get()应用&源码解析

    get()的使用非常简单,但不安全,因为其在获取值的时候,如果值存在,则直接返回封装在Optional中的值,如果不存在,则抛出NoSuchElementException。因此它的使用前提是已经确定Optional中有值,否则没有使用意义。
    在这里插入图片描述
    使用示例如下:

    Optional<Student> studentOptional =
    Optional.ofNullable(student);
    if (studentOptional.isPresent()){
      Student result = studentOptional.get();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.2.3 orElseThrow()应用&源码解析

    该方法与get()类似,都是用于取值,但是当Optional中没有值时,get()会直接抛出NoSuchElementException,这样的话,就存在了一定的局限性,因为有时可能需要抛出自定义异常。此时就可以使用orElseThrow(),它在取值时,如果Optional中没有值时,可以抛出自定义异常。
    在这里插入图片描述

    public class MyException extends Throwable {
      public MyException() {
        super();
     }
      public MyException(String message) {
        super(message);
     }
      @Override
      public String getMessage() {
        return "exception message";
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public class OrElseThrowDemo {
      public static void
    getStudentInfo(Student student) {
        Optional<Student> studentOptional =
    Optional.ofNullable(student);
       try {
          Student student1 =
    studentOptional.orElseThrow(MyException::new
    );
       } catch (MyException e) {
          e.printStackTrace();
       }
     }
      public static void main(String[] args) {
        Student student = null;
        getStudentInfo(student);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    2.2.4 map()应用&源码解析

    当Option中有值的话,经常性的一个操作就是从值中获取它的某一个属性,实例如下:

    if(job != null){
      String name = job.getName();
    }
    
    • 1
    • 2
    • 3

    对于这种需求,可以通过map()完成,它的使用思路与Stream中的map类似,只不过一个是转换Stream的泛型,一个是转换Optional的泛型。
    在这里插入图片描述
    使用示例如下:

    if (studentOptional.isPresent()){
     
      Optional nameOptional =
    studentOptional.map(Student::getName);
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2.5 flatMap()应用&源码解析

    刚才已经通过map()获取了学生的姓名,操作非常简单。但是当产生链路获取时,map可以使用么? 如:学生->工作->公司->公司名称。现在可能大家脑袋里已经有了一个想法,就是通过map(),代
    码结构如下:

    studentOptional.map(Student::getJob).map(Job:
    :getCompany).map(Company::getName);
    
    • 1
    • 2

    但是这段代码是无法通过编译的。因为根据map的学习,每一次在调用的时候,都会对Optional的泛型进行改变,最终产生多层Optional嵌套的结构。如下图所示:
    在这里插入图片描述
    对于这个问题的解决,Optional类中提供了另外一个获取值的方法flatMap()。它本身用于多层调用,同时对于结果它不会形成多个Optional,而是将结果处理成最终的一个类型的Optional。但是通过flatMap获取的返回值必须是Optional类型。而map则没有这个限制。
    在这里插入图片描述
    使用示例如下:

    Optional<String> nameOptional =
    studentOptional.flatMap(Student::getJob).flat
    Map(Job::getCompany).map(Company::getName);
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    2.2.6 filter()应用&源码解析

    在获取某个对象中的属性值时,经常会根据特定的条件进行获取。之前的编码方法通常为:

    Company company = optional().get();
    if("oldlu".equals(company.getName)){
      sout(company);
    }
    
    • 1
    • 2
    • 3
    • 4

    Optional类中也提供了数据过滤的方法filter()来实现这个需求。其会根据传入的条件进行判断,如果匹配则返回一个Optional对象并包含对应的值,否则返回一个空值的Optional
    在这里插入图片描述
    使用示例如下:

    Optional<Company> company =
    companyOptional.filter(c ->
    "oldlu".equals(c.getName()));
    
    • 1
    • 2
    • 3

    2.2.7 orElse()应用&源码解析

    在取值的时候,如果值不存在,有时我们会考虑返回一个默认值。该需求就可以通过orElse()实现。
    其内部会判断值是否为null,如果不为null,则返回该值,如果为null,则返回传入的默认值。
    在这里插入图片描述
    使用示例如下:

    public class Demo1 {
      public static void
    getCompanyName(Student student) {
        Optional<Student> studentOptional =
    Optional.ofNullable(student);
        if (studentOptional.isPresent()) {
           String value =
    studentOptional.flatMap(Student::getJob).fla
    tMap(Job::getCompany).map(Company::getName).
    orElse("default value");
          System.out.println(value);
       }
     }
      public static void main(String[] args) {
        Company company = new Company();
        //company.setName("oldlu");
        Optional<Company> companyOptional =
    Optional.of(company);
        Job job = new Job();
        job.setName("pm");
        job.setCompany(companyOptional);
        Optional<Job> jobOptional =
    Optional.of(job);
        Student s1 = new Student();
        s1.setName("张三");
        s1.setJob(jobOptional);
        getCompanyName(s1);
     }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.2.8 orElseGet()应用&源码解析

    orElseGet()也是用于当Optional中没有值时,返回默认值的方法。但是它与orElse()的区别在于,它是延迟加载的。只有当Optional中没有值是才会被调用。
    在这里插入图片描述
    区别示例如下:
    1)当公司名称不存在

    public class Demo1 {
      public static void
    getCompanyName(Student student) {
        Optional<Student> studentOptional =
    Optional.ofNullable(student);
        if (studentOptional.isPresent()) {
          String value1 =
    studentOptional.flatMap(Student::getJob).fla
    tMap(Job::getCompany).map(Company::getName).
    orElse(get("a"));
          String value2 =
    studentOptional.flatMap(Student::getJob).fla
    tMap(Job::getCompany).map(Company::getName).
    orElseGet(()->get("b"));
          System.out.println("a: 
    "+value1);
          System.out.println("b: 
    "+value2);
       }
     }
      public static String get(String name) {
        System.out.println(name + "执行了方
    法");
        return "exec";
     }
      public static void main(String[] args) {
        Company company = new Company();
        //company.setName("oldlu");
        Optional<Company> companyOptional =
    Optional.of(company);
        Job job = new Job();
        job.setName("pm");
        job.setCompany(companyOptional);
        Optional<Job> jobOptional =
    Optional.of(job);
        Student s1 = new Student();
    s1.setName("张三");
        s1.setJob(jobOptional);
        getCompanyName(s1);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    执行结果

    a执行了方法
    b执行了方法
    a:  exec
    b:  exec
    
    • 1
    • 2
    • 3
    • 4

    根据上述结果可知,当公司名称不存在时,orElse()与orElseGet()都被执行了。
    2)公司名称存在

    public class Demo1 {
      public static void
    getCompanyName(Student student) {
        Optional<Student> studentOptional =
    Optional.ofNullable(student);
        if (studentOptional.isPresent()) {
          String value1 =
    studentOptional.flatMap(Student::getJob).fla
    tMap(Job::getCompany).map(Company::getName).
    orElse(get("a"));
          String value2 =
    studentOptional.flatMap(Student::getJob).fla
    tMap(Job::getCompany).map(Company::getName).
    orElseGet(()->get("b"));
          System.out.println("a: 
    "+value1);
          System.out.println("b: 
    "+value2);
       }
     }
      public static String get(String name) {
        System.out.println(name + "执行了方
    法");
        return "exec";
     }
      public static void main(String[] args) {
        Company company = new Company();
        company.setName("oldlu");
        Optional<Company> companyOptional =
    Optional.of(company);
        Job job = new Job();
        job.setName("pm");
        job.setCompany(companyOptional);
        Optional<Job> jobOptional =
    Optional.of(job);
        Student s1 = new Student();
        s1.setName("张三");
        s1.setJob(jobOptional);
        getCompanyName(s1);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    执行结果如下:

    a执行了方法
    a:  oldlu
    b:  oldlu
    
    • 1
    • 2
    • 3

    根据上述结果可知,当公司名称存在时,orElseGet()不会被执行,而orElse()会被执行。因此可知,只有当Optional值为不存在时,orElseGet()才会被执行。在使用时,更加推荐使用orElseGet(),因为它使用延迟调用所以性能更加优异。

  • 相关阅读:
    上周热点回顾(6.17-6.23)
    Spark Streaming 整合 Flume
    计算机毕业设计Java餐饮掌上设备点餐系统(源码+系统+mysql数据库+lw文档)
    猿创征文 |简单入门linux命令
    MySQL 指定字段值排序
    Java 多线程编程
    Docker基础-cgroup
    web前端电影项目作业源码 大学生影视主题网页制作电影网页设计模板 学生静态网页作业成品 dreamweaver电影HTML网站制作
    .Net中跨域问题
    MySQL分布式集群搭建
  • 原文地址:https://blog.csdn.net/ZGL_cyy/article/details/126208479