合理利用Optional 来避免NPE

news/2024/11/15 6:58:30/

一、什么是Optional

在Java中什么异常最容易出现,那肯定是NullPointerException,空指针就像一个定时炸弹,总给我们带来些麻烦,在开发过程中都会碰到需要判断Null值以防止空指针的情况,以往的方式要么是抛异常,要么是if{}else{},直到Optional的出现,你可以更优雅的解决NPE问题。
首先我们来看一下 Optional的作者 Brian Goetz 对这个 API 的说明:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”, and using null for such was overwhelmingly likely to cause errors.

翻译过来大致意思:Optional提供了一个有限的机制让类库方法返回值清晰的表达有与没有值,避免很多时候 null 造成的错误
所以尝试在Java8+中使用工具类Optional,并在特定场景中简化代码逻辑。

二、Optional中有哪些常用方法

静态方法

  • Optional.of(T value):为指定的值创建一个指定非 null 值的 Optional,需要注意的是传入的参数不能为 null,否则抛出 NullPointerException。
  • Optional.ofNullable():为指定的值创建一个 Optional 对象,如果指定的参数为 null,不抛出异常,直接则返回一个空的 Optional 对象。

对象方法

  • isPresent():判断optional是否为空,如果空则返回false,否则返回true
  • get():如果 Optional 有值则将其返回,否则抛出 NoSuchElementException 异常
  • ifPresent(Consumer c):如果optional不为空,则将optional中的对象传给Comsumer函数orElse(T other):如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值
  • orElseGet(Supplier other):如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other
  • orElseThrow(Supplier exception):如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
  • filter(Predicate p):如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional
  • map(Function<T, U> mapper):如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中。
  • flatMap(Function< T,Optional< U >> mapper):跟上面一样,在optional不为空的情况下,将对象t映射成另外一个optional,区别:map会自动将u放到optional中,而flatMap则需要手动给u创建一个optional

三、Optional误用场景

1. 直接使用 isPresent() 进行 if 检查

这么写和直接判断空没啥区别,反而增加了麻烦

public class User{private String name;private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
// 错误使用OptionalUser u=null;Optional<User> userOpt=Optional.ofNullable(u);if(userOpt.isPresent()){String name=u.getName();System.out.println(name);}// 直接判断if(u !=null){String name=u.getName();System.out.println(name);}

可以接受的写法:建议使用 ifPresent(Consumer<? super T> consumer),判断Optional中是否有值,有值则执行 consumer,否则什么都不干,不建议直接使用isPresent() 进行 if 检查进行判断

userOpt.ifPresent(user -> System.out.println(user.getName()));

其实isPresent() 更建议用在流处理的结尾,用于判断是否符合条件

list.stream().filer(x -> Objects.equals(x,param)).findFirst().isPresent()

2. 在方法参数中使用 Optional或者在POJO中使用

Optional本身并没有实现序列化,现有的 JSON 序列化框架大多数也没有对此提供支持的。

public class User {private String name;private Integer age;// 不建议private Optional<String> address;
}

3. 直接使用 Optional.get

Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用 Optional.get()和不做任何空判断一样,十分危险,当没有值时会抛出一个NoSuchElementException异常

User u=null;
Optional<User> userOpt=Optional.ofNullable(u);
// 不建议
System.out.println(userOpt.get().getName());

得到得结果是:
在这里插入图片描述
获取 value 有三种方式:get() 、orElse()、 orElseGet()
get()不能直接使用,需要结合判空使用。这和!=null其实没多大区别,只是在表达和抽象上有所改善。
orElse() 和ElseGet() 区别
性能问题:无论如何都会执行括号中的内容, orElseGet()只在主体 value 是空时执行orElseGet()需要构建一个Supplier
使用不当,再次空指针,当orElse的参数是间接计算得来的时候。虽然这种说法有点牵强(因为并不是orElse导致了空指针异常),但是使用orElseGet确实可以避免这种情况

class User {// 中文名private String chineseName;// 英文名private EnglishName englishName;
}class EnglishName {// 全名private String fullName;// 简写private String shortName;
}

假如我们现在有User类,用户注册账号时,需要提供自己的中文名或英文名,或都提供,我们抽象出一个EnglishName类,它包含英文名的全名和简写(因为有的英文名确实太长了)。现在,我们希望有一个User#getName()方法,它可以像下面这样实现:当用户只提供了中文名时,此时englishName属性是null,但是在orElse中,englishName.getShortName()总是会执行。而在getName2()中,这个风险却没有。

class User {// ... 之前的内容public String getName1() {return Optional.ofNullable(chineseName).orElse(englishName.getShortName());}public String getName2() {return Optional.ofNullable(chineseName).orElseGet(() -> englishName.getShortName());}
}

如果只是简单的返回一个静态资源、字符串等等,直接返回静态资源使用orElse()即可。

4. 使用在注入的属性中

// 一般不建议
public class CommonService {private Optional<UserService> userService;
}

四、Optional正确使用姿势

1.将对象某个属性赋值给其他实体对象

// 调用服务层查询数据库获得目标对象
ProdSku prodSku = prodSKUService.getFirstProdSku(p.getProdId());
if (prodSku != null) {prodAPP.setPrice(prodSku.getPrice());
}
// 使用Optional 和函数式编程,一次完成
Optional.ofNullable(prodSku).ifPresent(p -> prodAPP.setPrice(p.getPrice()));

2.当取不到值时,返回你指定的 default

如果指定值固定的,明确的建议用orElse(),如果指定值是不明确的或者是需要大量计算得出的建议用orElseGet()

2.1判断一个集合大小,但又怕这个集合是null

List<User> list=null;
Optional<List<User>> optional=Optional.ofNullable(list);
int size = optional.map(List::size).orElse(0);
// 输出0
System.out.println(size);
// 直接调用集合的size方法就有可能造成 NullPointerException 空指针异常
System.out.println(list.size());

2.2实体类get方法初始化赋值

// 当传给前端同学一个字段时候,并不希望这个值出现null,但数据库里确实就是null的情况
public BigDecimal getAccount() {if (account == null) {account = BigDecimal.ZERO;}return account;
}
// 代替:
public BigDecimal getAccount() {return Optional.ofNullable(account).orElse(BigDecimal.ZERO) ;
}

2.3 类型之间转换,当没有值时候就返回一个默认值

String price=null;
Double c=Optional.ofNullable(price).map(a->Double.valueOf(a)).orElse(0.00);
System.out.println(c);
// 直接转换必定空指针错误
Double b=Double.valueOf(price);

2.4获取对象对应的汉字信息

如果是null就直接返回不获取,不论是a是null,还是对a运算是null,都能走到orElse 里面去的

Optional<Integer> optional=Optional.ofNullable(super.getAduitStatus());return optional.map(a->AduitStatusEnum.name(String.valueOf(a))).orElse(null);

3.多层属性获取(获取一个多层嵌套实体的值,收益最明显的一个例子)

 //+++++++++++++++++++++++模拟这个要取的对象从其他接口传过来是不是null++++++++++++++++++++++++++++++++++++InetSocketAddress inetSocketAddress = new InetSocketAddress(2);String re="小明取不到值";if(inetSocketAddress !=null){if(inetSocketAddress.getAddress() !=null){if(inetSocketAddress.getAddress().getHostName() !=null){String name2 = inetSocketAddress.getAddress().getHostName().toUpperCase();if(StringUtils.isNotBlank(name2) ){re=name2;}}}}System.out.println(re);Optional<InetSocketAddress> optional=Optional.ofNullable(inetSocketAddress);String op= optional.map(a ->a.getAddress()).map(b ->b.getHostName()).map(c->c.toUpperCase()).orElse("小王也取不到值");System.out.println(op);//+++++++++++++++++++++++模拟这个要取的对象从其他接口传过来是null++++++++++++++++++++++++++++++++++++String re2="小明取不到值";InetSocketAddress inetSocketAddress2 = null;if(inetSocketAddress2 !=null){if(inetSocketAddress2.getAddress() !=null){if(inetSocketAddress2.getAddress().getHostName() !=null){String name2 = inetSocketAddress2.getAddress().getHostName().toUpperCase();if(name2 ==null){re2=name2;}}}}System.out.println(re2);Optional<InetSocketAddress> optional2=Optional.ofNullable(inetSocketAddress2);String op2= optional2.map(a ->a.getAddress()).map(b ->b.getHostName()).map(c->c.toUpperCase()).orElse("小王也取不到值");System.out.println(op2);

运行结果如下图所示
在这里插入图片描述

4.阻塞性业务场景推荐使用

BigDecimal stock = Optional.ofNullable(lcStockObj).map(a -> a.getAssetNum()).orElseThrow(() -> new RuntimeException("库存数据不存在,出入库失败"));

参考链接:https://blog.csdn.net/rhythm_1/article/details/121716010


http://www.ppmy.cn/news/73610.html

相关文章

Linux Audio (4) DAPM-1 Kcontrol

DAPM-1 Kcontrol 控制部件之kcontrolsnd_kcontrol_new 结构体如何定义snd_kcontrol_new?如何使用snd_kcontrol&#xff1f;添加kcontrol代码分析 课程&#xff1a;韦东山音频专题 内核&#xff1a;Kernel 3.5 但是我用的实例和课程不同&#xff0c;以防止编程记流水账 控制部件…

Evita项目-2-Evita中规定的安全汽车车载电子网络架构

目录 1 摘要 2 文章目的 3 安全需求 4 安全架构 4.1 硬件和软件分区 4.2 EVITA硬件安全模块

【嵌入式Linux】设备树基本语法

设备树基本语法 1_总领-本期设备树视频要怎么讲&#xff1f;讲什么&#xff1f;_哔哩哔哩_bilibili 基本的 特殊的 中断控制 描述GIC控制器 时钟 CPU GPIO 个数&#xff0c;保留范围&#xff08;起始、长度&#xff09;&#xff0c;个数对应的名字 GPIO映射-这个脚被用了换一…

AC,AP以及三阶段项目

特点&#xff1a;access&#xff1a;连接终端设备 只能通过1个vlan trunk&#xff1a;交换机与交换机相连 可以通过多个vlan 共同特点&#xff1a;交换机的端口收发数据的规则&#xff1a; 收&#xff1a;如果收到的数据&#xff0c;没有携带任何标签&#xff0c;则使用该端口…

Linux---目录结构、绝对路径与相对路径、命令基础格式、ls命令

1. Linux的目录结构 Linux的目录结构是一个树型结构。 Windows 系统可以拥有多个盘符, 如 C盘、D盘、E盘。 Linux没有盘符这个概念, 只有一个根目录 /, 所有文件都在它下面。 在Linux系统中&#xff0c;路径之间的层级关系&#xff0c;使用&#xff1a;/ 来表示。 Linux只…

nerfstudio介绍及在windows上的配置、使用

nerfstudio提供了一个简单的API&#xff0c;可以简化创建、训练和可视化NeRF的端到端过程。该库通过模块化每个组件来支持可解释的NeRF实现。nerfstudio源码地址: https://github.com/nerfstudio-project/nerfstudio , 通过模块化集成了多个NeRF扩展的实现&#xff0c;持续更新…

企业数字化转型到底该怎么做?

企业数字化转型涉及实施技术和利用数字工具来增强业务流程、改善客户体验和推动创新。主要包括&#xff1a; 愿景和战略&#xff1a;首先明确定义数字化转型目标。确定数字技术可以对企业的业务产生最重大影响的领域&#xff0c;例如运营效率、客户参与度或产品开发。 评估当前…

知识图谱构建全流程

一、知识图谱简介 知识图谱&#xff0c;是结构化的语义知识库&#xff0c;用于迅速描述物理世界中的概念及其相互关系&#xff0c;通过知识图谱能够将Web上的信息、数据以及链接关系聚集为知识&#xff0c;使信息资源更易于计算、理解以及评价&#xff0c;并能实现知识的快速响…