《开发实战》11 | 空值处理:分不清楚的null和恼人的空指针

news2024/12/26 11:33:57

11 | 空值处理:分不清楚的null和恼人的空指针

修复和定位恼人的空指针问题

NullPointerException 是 Java 代码中最常见的异常,最可能出现的场景归为以下5 种:

  1. 参数值是 Integer 等包装类型,使用时因为自动拆箱出现了空指针异常;
  2. 字符串比较出现空指针异常;
  3. 诸如 ConcurrentHashMap 这样的容器不支持 Key 和 Value 为 null,强行 put null 的Key 或 Value 会出现空指针异常;
  4. A 对象包含了 B,在通过 A 对象的字段获得 B 之后,没有对字段判空就级联调用 B 的方法出现空指针异常;
  5. 方法或远程服务返回的 List 不是空而是 null,没有进行判空就直接调用 List 的方法出现空指针异常。

有时候线上的空指针异常是很难排查的,因为是不能打断点的,往往是将代码进行拆分,或者添加日志。
可以考虑使用Arthas 简单易用功能强大,可以定位出大多数的 Java 生产问题。
如果是分支复杂的业务逻辑,你需要再借助 stack 命令来查看 wrongMethod 方法的调用栈,并配合 watch 命令查看各方法的入参,就可以很方便地定位到空指针的根源了
**定位到空指针异常后,我们需要探讨这是入参问题还是bug **:
如果是来源于入参,还要进一步分析入参是否合理等;
如果是来源于 Bug,那空指针不一定是纯粹的程序 Bug,可能还涉及业务属性和接口调用规范等。

修复方式,一般都是使用else-if,我们也可以使用 Java 8 的 Optional 类。
使用判空方式或 Optional 方式来避免出现空指针异常,不一定是解决问题的最好方式,空指针没出现可能隐藏了更深的 Bug。
解决空指针异常,还是要真正 case by case 地定位分析案例,然后再去做判空处理,而处理时也并不只是判断非空然后进行正常业务流程这么简单,同样需要考虑为空的时候是应该出异常、设默认值还是记录日志等

POJO 中属性的 null 到底代表了什么?

需要考虑:

  1. DTO 中字段的 null 到底意味着什么?是客户端没有传给我们这个信息吗?
  2. 既然空指针问题很讨厌,那么 DTO 中的字段要设置默认值么?
  3. 如果数据库实体中的字段有 null,那么通过数据访问框架保存数据是否会覆盖数据库中的既有数据?

User 的 POJO,同时扮演 DTO 和数据库 Entity 角色,包含用户 ID、姓名、昵称、年龄、注册时间等属性:

@Data
@Entity
public class User {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  private String name;
  private String nickname;
  private Integer age;
  private Date createDate = new Date();
}

有一个 Post 接口用于更新用户数据,根据用户姓名自动设置一个昵称,昵称的规则是“用户类型 + 姓名”

@Autowired
private UserRepository userRepository;
@PostMapping("wrong")
public User wrong(@RequestBody User user) {
  user.setNickname(String.format("guest%s", user.getName()));
  return userRepository.save(user);
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

数据库中初始化一个用户,age=36、name=zhuye、create_date=2020 年 1 月4 日、nickname 是 NULL:
image.png

  1. 调用方只希望重置用户名,但 age 也被设置为了 null;
  2. nickname 是用户类型加姓名,name 重置为 null 的话,访客用户的昵称应该是guest,而不是 guestnull,重现了文首提到的那个笑点;
  3. 用户的创建时间原来是 1 月 4 日,更新了用户信息后变为了 1 月 5 日。

原因

  1. 明确 DTO 种 null 的含义。对于 JSON 到 DTO 的反序列化过程,null 的表达是有歧义的,客户端不传某个属性,或者传 null,这个属性在 DTO 中都是 null。
  2. POJO 中的字段有默认值。如果客户端不传值,就会赋值为默认值,导致创建时间也被更新到了数据库中。
  3. 注意字符串格式化时可能会把 null 值格式化为 null 字符串。比如上面的 guestnull
  4. DTO 和 Entity 共用了一个 POJO。
  5. 数据库字段允许保存 null,会进一步增加出错的可能性和复杂度。

解决:DTO 和 Entity 进行拆分

  1. UserDto 中只保留 id、name 和 age 三个属性,且 name 和 age 使用 Optional 来包装,以区分客户端不传数据还是故意传 null。
  2. 在 UserEntity 的字段上使用 @Column 注解,把数据库字段 name、nickname、age和 createDate 都设置为 NOT NULL,并设置 createDate 的默认值为CURRENT_TIMESTAMP,由数据库来生成创建时间。
  3. 使用 Hibernate 的 @DynamicUpdate 注解实现更新 SQL 的动态生成,实现只更新修改后的字段,不过需要先查询一次实体,让 Hibernate 可以“跟踪”实体属性的当前状态,以确保有效。
@Data
public class UserDto {
  private Long id;
  private Optional<String> name;
  private Optional<Integer> age;
}
@Data
@Entity
@DynamicUpdate
public class UserEntity {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  @Column(nullable = false)
  private String name;
  @Column(nullable = false)
  private String nickname;
  @Column(nullable = false)
  private Integer age;
  @Column(nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIM
  private Date createDate;
}

小心 MySQL 中有关 NULL 的三个坑

NULL 字段,和你着重说明 sum 函数、count 函数,以及 NULL 值条件可能踩的坑

@Entity
@Data
public class User {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  private Long score;
}

往实体初始化一条数据,其 id 是自增列自动设置的 1,score 是NULL

可能出现的坑:

  1. 通过 sum 函数统计一个只有 NULL 值的列的总和,比如 SUM(score);
    1. SELECT SUM(score) FROM user
  2. select 记录数量,count 使用一个允许 NULL 的字段,比如 COUNT(score);
    1. SELECT COUNT(score) FROM user
  3. 使用 =NULL 条件查询字段值为 NULL 的记录,比如 score=null 条件。
    1. SELECT * FROM user WHERE score=null

得到的结果,分别是 null、0 和空 List
原因是:

  1. MySQL 中 sum 函数没统计到任何记录时,会返回 null 而不是 0,可以使用 IFNULL函数把 null 转换为 0;
  2. MySQL 中 count 字段不统计 null 值。COUNT(*) 才是统计所有记录数量的正确方式。
  3. MySQL 中 =NULL 并不是判断条件而是赋值,对 NULL 进行判断只能使用 IS NULL 或者 IS NOT NULL。

修改sql:

  1. SELECT IFNULL(SUM(score),0) FROM user
  2. SELECT COUNT(*) FROM user
  3. SELECT * FROM user WHERE score IS NULL

得到三个正确结果,分别为 0、1、[User(id=1, score=null)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/946488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

02调制+滤波器+冲激函数的傅立叶变换

目录 一、调制方式 1.1 什么是调制&#xff1f; 1.2 为什么要调制&#xff1f; 1.3 如何调制&#xff1f; 1.4 调制包含的信号类型&#xff1f; 1. 消息信号 2. 载波信号 3. 调制信号 1.5 调制类型&#xff1f; 1. 调幅 2. 调频 3. 调相 4. 模拟脉冲调制 5. 脉冲…

07:STM32----ADC模数转化器

目录 1:简历 2:逐次逼近型ADC 3:ADC基本结构 4:输入通道 5:规则组的4种转换模式 1:单次转化,非扫描模式 2:连续转化,非扫描模式 3:单次转化,扫描模式 4:单次转化,扫描模式 6:触发控制 7:数据对齐 8:转化时间 9:校准 10:ADC的硬件电路 A: AD单通道 1:连接图 2:函…

单变量图的类型与直方图绘图基础

文章目录 单变量图的类型1.直方图&#xff08;histogram plot&#xff09;2.密度图&#xff08;density plot&#xff09;3.Q-Q 图&#xff08;Quantile- Quantile plot&#xff0c;又称分位图&#xff09;4.P-P 图&#xff08;Probability-Probability plot&#xff09;5.经验…

illegal cyclic inheritance involving trait Iterable_2种解决方式

一、报错内容 /Users/liyangda/Code/DemoProject/demo-scala/src/scala/old04/T4.scala:11:20 illegal cyclic inheritance involving trait Iterableval value List(1, 2, 3, 4, 5, 6, 7, 8)二、问题解决 1、方式一&#xff1a;降低scala版本 可以选择降低Scala的版本&…

《探花交友》之开篇

《探花交友》 功能介绍项目介绍工程搭建短信验证码实现用户登录功能前后端开发视频及相关资源领取Q作者 1、功能介绍 探花交友是一个陌生人的在线交友平台&#xff0c;在该平台中可以搜索附近的人&#xff0c;查看好友动态&#xff0c;平台还会通过大数据计算进行智能推荐&a…

若依自定义详情页(传多个参数)

【版权所有&#xff0c;文章允许转载&#xff0c;但须以链接方式注明源地址&#xff0c;否则追究法律责任】【创作不易&#xff0c;点个赞就是对我最大的支持】 前言 仅作为学习笔记&#xff0c;供大家参考 总结的不错的话&#xff0c;记得点赞收藏关注哦&#xff01; 不知道…

gerrit 如何提交进行review

前言 本文主要介绍如何使用gerrit进行review。 下述所有流程都是参考&#xff1a; https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough.html 先给一个commit后但是还没有push上去的一个办法&#xff1a; git reset --hard HEAD^可以多次reset.…

5V升压充电16.8V芯片充电管理IC

航誉微HU5912四节锂电池升压充电IC 概要 HU5912是一款 5V输入&#xff0c;支持四节锂电池的升压充电管理 IC。 HU5912 &#xff0c;采用异步开关架构&#xff0c;使其在应用时仅需 要极少的外围器件&#xff0c;可有效减少整体方案尺寸&#xff0c;降低 BOM 成本。 HU5912…

腾讯云便宜购买指南(腾讯云怎样购买划算)

腾讯云是国内知名的云计算服务商&#xff0c;拥有广泛的应用和用户群体。对于有需要的用户来说&#xff0c;怎样便宜购买腾讯云产品是一个值得关注的问题&#xff0c;下面给大家分享腾讯云便宜购买指南。 腾讯云便宜购买指南&#xff1a;1、新用户专属礼包&#xff1b;2、老用户…

网路日志服务器

网络日志服务器是用于集中存储、管理和分析网络设备生成的日志数据的服务器。它扮演着收集、存储和分析网络日志的关键角色&#xff0c;对于网络安全、故障排除和性能优化等方面具有重要意义。 网络日志服务器的工作原理是通过与网络设备建立连接&#xff0c;接收并保存设备生成…

《人月神话》:chapter 4 系统设计中的“专制”和“民主”

以下总结来自于《人月神话》 第四章 &#xff1a;贵族制&#xff0c;民主制和系统设计 系统设计中最重要的因素&#xff1a;概念完整性 1.设计必须由一个人或者具有共识的小型团队来完成 2.大型系统架构设计与具体实现相分离 3.必须有人控制概念&#xff0c;确保完整性&…

WebSocket- 前端篇

官网代码 // 为了浏览器兼容websocketconst WebSocket window.WebSocket || window.MozWebSocket// 创建连接 this.socket new WebSocket(ws://xxx)// 连接成功this.socket.onopen (res)>{console.log(websocket 连接成功)this.socket.send(入参字段) // 传递的参数字段}…

【Android】AES解密抛出异常Cipher functions:OPENSSL_internal:WRONG_FINAL_BLOCK_LENGTH

Java使用AES加密的时候没得问题&#xff0c;但是在解密的时候就出错了&#xff0c;一起来找找原因吧。 首先&#xff0c;Java运行的代码如下&#xff0c;使用AES加解密 Cipher cipher Cipher.getInstance("AES/CBC/NOPadding"); //...主要问题 可调试运行控制台抛…

Figma怎么导出源文件,超详细教程来了

Figma怎么导出源文件&#xff0c;超详细教程来了要说近几年话题最多的界面设计软件&#xff0c;Figma当之无愧。用一句话去定义Figma&#xff0c;它是一款基于浏览器的全能型设计工具。Figma允许设计师、开发者和其他团队成员无论身在何处&#xff0c;都可以共同协作创建和编辑…

FastStone Capture

FastStone Capture 简介下载安装注册 简介 FastStone Capture是一款用于屏幕截图和屏幕录制的工具。它允许用户捕捉屏幕上的内容&#xff0c;并将其保存为图像文件&#xff0c;还可以录制屏幕活动为视频文件。 FastStone Capture官网: https://www.faststone.org/FSCaptureDet…

JDK源码解析-ArrayList

1. ArrayList类 1.1 ArrayList类结构图 ArrayList 是一个用数组实现的集合&#xff0c;支持随机访问&#xff0c;元素有序且可以重复。 &#xff08;1&#xff09;ArrayList 是一种变长的集合类&#xff0c;基于定长数组实现。 &#xff08;2&#xff09;ArrayList 允许空值…

[Android 四大组件] --- Activity

1 Activity是什么 ​​Activity​​是一个Android的应用组件&#xff0c;它提供屏幕进行交互。每个Activity都会获得一个用于绘制其用户界面的窗口&#xff0c;窗口可以充满哦屏幕也可以小于屏幕并浮动在其他窗口之上。 一个应用通常是由多个彼此松散联系的Activity组成&…

NFTScan 正式上线 Base NFTScan 浏览器和 NFT API 数据服务

2023 年 8 月 24 号&#xff0c;NFTScan 团队正式对外发布了 Base NFTScan 基础设施&#xff0c;将为 Base 生态的 NFT 开发者和用户提供简洁高效的 NFT 数据搜索查询服务。NFTScan 作为全球领先的 NFT 数据基础设施服务商&#xff0c;Base 是继 Bitcoin、Ethereum、BNBChain、…

合宙Air724UG LuatOS-Air LVGL API控件--日历 (Calendar)

日历 (Calendar) LVGL 提供了一个用来选择和显示当前日期的日历控件。 示例代码 – 高亮显示的日期 highlightDate lvgl.calendar_date_t() – 日历点击的回调函数 – 将点击日期设置高亮 function event_handler(obj, event) if event lvgl.EVENT_VALUE_CHANGED then da…

Web项目与帆软11集成后通过项目访问cpt文件会弹出数据决策系统登录界面如何取消

1、登录帆软 - 数据决策系统 * 点击 &#xff1a;管理系统 - 模板认证 - 点击设置按钮 - 关闭 2、选择关闭单个认证 你点击后&#xff0c;它默认所有都是开的。你依次点击关闭&#xff0c;然后再把要的模板点击开启&#xff0c;如下图所示&#xff1a;第一个就表示开了认证&am…