【Java笔记】Optional如何处理空指针异常 & 为什么不建议get()

Why Optional

需求

从数据库中根据Name查询对应用户

正常流程

  • 定义一个User

    public class User {
        String name;
        String fullName;
    
        public User(String name, String fullName) {
            this.name = name;
            this.fullName = fullName;
        }
    
        public String getName() {
            return name;
        }
    
        public String getFullName() {
            return fullName;
        }
    }
    
  • 定义一个Respository类,模仿数据库查询

    public class UserRepository {
        public User findUserByName(String name){
            // 模仿一下从数据库中查询的逻辑
            if (name.equals("Hansdas")){
                return new User("Hansdas","Hansdas Chen");
            }else {
                // 查不到则返回null
                return null;
            }
        }
    }
    
  • 在main中尝试查询

    public class Main {
        public static void main(String[] args) {
            UserRepository userRepository = new UserRepository();
            User user = userRepository.findUserByName("Hansdas");
            System.out.println(user.getFullName());
        }
    }
    

    输出如下,查询成功

    Hansdas Chen
    

空指针异常NullPointerException

但此时如果查询一个不存在的name,比如"Hansbas",findUserByName返回一个null对象,调用null的getFullName()时,则会出现空指针异常NullPointerException

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "User.getFullName()" because "user" is null
	at Main.main(Main.java:6)
  • NullPointerException:Java中尝试访问一个空引用的属性,或调用一个空引用的方法的时候,就会抛出空指针异常

处理空指针异常

一般最容易想到的就是用if (xx != null){...}进行显式处理,比如:

    public static void main(String[] args) {
        UserRepository userRepository = new UserRepository();
        User user = userRepository.findUserByName("Hansdas2");
        if (user != null){
            System.out.println(user.getFullName());
        }
    }

缺点

实际项目中需要处理大量可能为null的值,如果都用if (xx != null){...}处理,会使得代码很难臃肿,难以维护

因此需要Optional

What’s Optional

Optional 类是一个可以为null的容器对象,提供了很多方法,帮助解决空指针异常问题,不用到处显式地用if (xx != null){...}做空指针检测。

这里的重点其实就两个:

  • Optional是一个容器,用来装某种类型(T)的值,也可以是null
  • Optional可以用来处理空指针异常

Optional 基本用法

创建空的Optional对象

  • 获取Optional对象

    Optional<Object> optionalBox = Optional.empty(); // 创建一个空的Optional对象
    

检测Optional对象内部值的状态 :isPresent() &isEmpty()

System.out.println(optionalBox.isPresent());  // 检测Optional对象是否存在值,存在返回true
System.out.println(optionalBox.isEmpty()); //  与isPresent相反,不存在返回true
false
true

将值放入Optional: of()&ofNullable()

  • of():用于已知非null的情况

    String name = "Hansdas";
    Optional<String> optionalBox = Optional.of(name);
    System.out.println(optionalBox.isPresent());
    System.out.println(optionalBox.isEmpty());
    
    true
    false
    
    • 如果传入对象为null,会抛出空指针异常

      String name = null;
      Optional<String> optionalBox = Optional.of(name);
      System.out.println(optionalBox.isPresent());
      System.out.println(optionalBox.isEmpty());
      
  • ofNullable():用于不知道处理对象是否为null的情况

    String name = null;
    Optional<String> optionalBox = Optional.ofNullable(name);
    System.out.println(optionalBox.isPresent());
    System.out.println(optionalBox.isEmpty());
    
    false
    true
    

将值取出 get()

  • get():很直观,但不建议
String getName = optionalBox.get();

常用方法

方法描述
static <T> Optional<T> empty()返回空的 Optional 实例。
boolean equals(Object obj)判断其他对象是否等于 Optional。
Optional<T> filter(Predicate<? super <T> predicate)如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
Optional flatMap(Function<? super T,Optional> mapper)如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
T get()如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
int hashCode()返回存在值的哈希码,如果值不存在 返回 0。
void ifPresent(Consumer<? super T> consumer)如果值存在则使用该值调用 consumer , 否则不做任何事情。
boolean isPresent()如果值存在则方法会返回true,否则返回 false。
Optional<U> map(Function<? super T,? extends U> mapper)如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
static <T> Optional<T> of(T value)返回一个指定非null值的Optional。
static <T> Optional<T> ofNullable(T value)如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
T orElse(T other)如果存在该值,返回值, 否则返回 other。
T orElseGet(Supplier<? extends T> other)如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
String toString()返回一个Optional的非空字符串,用来调试

Optinal实现需求

现在用Optional去修改UserRepository,来解决查询时可能的空指针异常:

  • 修改UserRepository

    public class UserRepository {
        public Optional<User> findUserByName(String name){
            // 模仿一下从数据库中查询的逻辑
            if (name.equals("Hansdas")){
                return Optional.of(new User("Hansdas","Hansdas Chen"));
            }else {
                // 查不到则返回null
                return Optional.empty();
            }
        }
    }
    

为什么不建议使用.get()

  • 在main中获取一下

    public class Main {
        public static void main(String[] args) {
            UserRepository userRepository = new UserRepository();
            Optional<User> optionalUser = userRepository.findUserByName("Hansdas");
            User user = optionalUser.get();
            System.out.println(user.getFullName());
        }
    }
    
    • 如果查询的name不存在,get()会抛出异常,但是NoSuchElementException,而不是user.getFullName()抛出的NullPointerException
  • 很自然地就想到,加个if判断下!

    public class Main {
        public static void main(String[] args) {
            UserRepository userRepository = new UserRepository();
            Optional<User> optionalUser = userRepository.findUserByName("Hansdas2");
            if (optionalUser.isPresent()){
                // Optional容器不为空
                User user = optionalUser.get();
                System.out.println(user.getFullName());
            }else{
                User defaultUser = new User("Default", "Default Name");
                System.out.println(defaultUser.getFullName());
            }
        }
    }
    

这个时候的代码又跟引入Optional之前的代码没啥区别了,依旧用命令式编程(imperative programming)的方式做判断和检查,并没有实现Optional ”减少臃肿,便于维护“的初衷

应当使用函数式编程的方式,来完成Optional的正确使用

Optional 常用的操作方式

下面详细说说Optional一些常用的操作(如何实现”减少臃肿,便于维护“的初衷)

获取值

  • orElse()获取值,如果容器为空则返回默认值

    UserRepository userRepository = new UserRepository();
    Optional<User> optionalUser = userRepository.findUserByName("Hansdas2");
    User user = optionalUser.orElse(new User("Default", "Default Name"));
    System.out.println(user.getFullName());
    
  • orElseGet():使用lambda表达式实现Supplier接口,Optional容器为空执行

    User user1 = optionalUser.orElseGet(() -> new User("Default", "Default Name"));
    System.out.println(user1.getFullName());
    
  • orElse()orElseGet()的区别:

    • orElse()不论Optional对象空或非空,都会执行传入参数 new User("Default", "Default Name")
    • orElseGet()仅当Optional对象为空式,执行传入参数 new User("Default", "Default Name")
    • 当默认值确定,且获取默认值的代价不会很高时可以使用orElse()
    • 如果获取默认值代价比较高(比如需要进行一些计算),建议用orElseGet()
  • orElseThrow():默认跟get一样,为空时抛出NoSuchElementException,也可以实现Supplier接口自定义抛出异常

    optionalUser.orElseThrow();
    optionalUser.orElseThrow(() -> new RuntimeException("User not found"));
    

根据值的不同状态进行操作

  • ifPresent()实现cosumer函数式接口,如果Optional含值,执行lambda里的方法,不含值则不操作

    UserRepository userRepository = new UserRepository();
    Optional<User> optionalUser = userRepository.findUserByName("Hansdas");
    optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
    
  • ``ifPresentOrElse()`:用两个lambda表达式实现Consumer和Runnable函数式接口

    • Optional包含值的时候执行一个操作(Consumer),不包含值的时候执行另一个操作(Runnable)
    Optional<User> optionalUser = userRepository.findUserByName("Hansdas2");
    optionalUser.ifPresentOrElse(
            user -> System.out.println(user.getFullName()),
            () -> System.out.println("User Not Found")
    );
    
  • filter():根据给定的条件来决定是都保留该值

    Optional<User> optionalUser1 = optionalUser.filter(
    	user -> user.getFullName().equals("Hansdas Chen")
    );
    System.out.println(optionalUser1.isPresent());
    
  • map():对Optional中的值进行转换

    • 不会改变原始的Optional对象,而是返回一个新的Optional对象(包含转换后的值 )。
    • 如果Optional是空的,直接返回一个空的Optional对象
    // User类型的Optional -> String类型的Optional
    Optional<String> optionalFullName = optionalUser.map(user -> user.getFullName());
    
    // 可以用method reference简化lambda表达式
    Optional<String> optionalFullName = optionalUser.map(User::getFullName); 
    
  • flatMap():可以用于嵌套的Optional情况:

    • 如果getFullName返回的是一个Optional对象

      public class User {
      	...
          public Optional<String> getFullName() {
              return Optional.ofNullable(fullName);
          }
      }
      
    • main中使用flatMap与map

      // map会有嵌套
      Optional<Optional<String>> optionalFullName = optionalUser.map(User::getFullName); 
      // flatMap没有嵌套
      Optional<String> optionalFullName2 = optionalUser.flatMap(User::getFullName);
      
  • stream():可以将Optional对象转化为一个Stream对象,对其中的值进行流操作

    • 如果Optional对象包含值,则将其封装到一个Stream流中
    • 如果Optional对象为空,则返回一个空的Stream
Stream<String> a = optionalUser
                    .map(User::getName)
                    .stream(); // 返回一个String流
a.forEach(System.out::println);

总结

Optional普遍用于方法的返回类型,表示方法可能不返回结果(即返回null)

Optional不适用的场景

  • 不适合用于类的字段:因为Optional的管理有一定的额外开销,作为字段类型会增加内存消耗,并且会使得对象的序列化变得复杂

    // 不建议
    public class User{
    	Optional<String> name;
    	// ...
    }
    
  • 不适合用于方法的参数:会使方法的使用和理解变得复杂

    // 不建议
    public class User{
    	public void updateUser(Optional<String> name){
            // ...
        }
    	// ...
    }
    
    • 如果希望函数参数接受以一个可能为空的值,可以通过方法重载等方式实现

      public class User{
      	public void updateUser(){
              // ...
          }
      	public void updateUser(Optional<String> name){
              // ...
          }
      	// ...
      }
      
  • 不适合用于构造器参数:会迫使调用者创造Optional实例,增加开销

    // 不建议
    public class User{
    	public User(Optional<String> name){
    		// ...
    	}
    }
    
    • 如果希望构造器参数接受以一个可能为空的值,也可以通过方法重载等方式实现

      public class User{
      	public User(){
      		// ...
      	}
      	public User(Optional<String> name){
      		// ...
      	}
      }
      
  • 不适合用作集合的参数类型:集合本身就可以处理空集合的情况,没必要用Optional包装

    // 不建议
    Optional<List<User>> ...
    
    Collection.emptyList()
    
  • 不建议使用get()方法:无法确认是否有值,空的Optional会抛出NoSuchElementException。如果用isPresent()先检查Optional是否为空,再get(),跟直接检查对象是否为null没什么区别

    • 可以使用ifPresent, OrElse, orElseThrow等方法

Reference

Java中的Optional - Optionals in Java

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值