Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
51940 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
代码篇 (23讲)
Java 业务开发常见错误 100 例
15
15
1.0x
00:00/00:00
登录|注册

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

你好,我是朱晔。今天,我要和你分享的主题是,空值处理:分不清楚的 null 和恼人的空指针。
有一天我收到一条短信,内容是“尊敬的 null 你好,XXX”。当时我就笑了,这是程序员都能 Get 的笑点,程序没有获取到我的姓名,然后把空格式化为了 null。很明显,这是没处理好 null。哪怕把 null 替换为贵宾、顾客,也不会引发这样的笑话。
程序中的变量是 null,就意味着它没有引用指向或者说没有指针。这时,我们对这个变量进行任何操作,都必然会引发空指针异常,在 Java 中就是 NullPointerException。那么,空指针异常容易在哪些情况下出现,又应该如何修复呢?
空指针异常虽然恼人但好在容易定位,更麻烦的是要弄清楚 null 的含义。比如,客户端给服务端的一个数据是 null,那么其意图到底是给一个空值,还是没提供值呢?再比如,数据库中字段的 NULL 值,是否有特殊的含义呢,针对数据库中的 NULL 值,写 SQL 需要特别注意什么呢?
今天,就让我们带着这些问题开始 null 的踩坑之旅吧。

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

NullPointerException 是 Java 代码中最常见的异常,我将其最可能出现的场景归为以下 5 种
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 业务开发常见错误 100 例》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(26)

  • 最新
  • 精选
  • Darren
    补充下:在MySQL的使用中,对于索引列,建议都设置为not null,因为如果有null的话,MySQL需要单独专门处理null值,会额外耗费性能。 回答下问题: 第一个问题: 从ConcurrentHashMap他自己的作者(Doug Lea): http://cs.oswego.edu/pipermail/concurrency-interest/2006-May/002485.html The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.get(key) returns null, you can't detect whether the key explicitly maps to null vs the key isn't mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls. ConcurrentMaps(ConcurrentHashMaps,ConcurrentSkipListMaps)不允许使用null的主要原因是,无法容纳在非并行映射中几乎无法容忍的歧义。最主要的是,如果map.get(key)return null,则无法检测到该键是否显式映射到null该键。在非并行映射中,您可以通过进行检查 map.contains(key),但在并行映射中,两次调用之间的映射可能已更改。 hashtable也是线程安全的,所以也是key和value也是不可以null的 treeMap 线程不安全,但是因为需要排序,进行key的compareTo方法,所以key是不能null中,value是可以的 第二个问题: MyBatis @Column注解的updateIfNull属性,可以控制,当对应的列value为null时,updateIfNull的true和false可以控制

    作者回复: 👍🏻

    11
    70
  • Asha
    老师,麻烦问下 UserDto 中只保留 id、name 和 age 三个属性,且 name 和 age 使用 Optional 来包装,以区分客户端不传数据还是故意传 null, 这个我不太明白是怎么区分出来的呢? 还有下面的这句话,他能走到orElse上吗? if (user.getName() != null) { userEntity.setName(user.getName().orElse("")); }

    作者回复: 文中有说『由于 DTO 中已经巧妙使用了 Optional 来区分客户端不传值和传 null 值,那么业务逻辑实现上就可以按照客户端的意图来分别实现逻辑。如果不传值,那么 Optional 本身为 null,直接跳过 Entity 字段的更新即可,这样动态生成的 SQL 就不会包含这个列;如果传了值,那么进一步判断传的是不是 null』 你可以试一下,JSON不传这个属性,Optional直接是null,如果传了属性值为null,那么Optional其实就是Optional.empty(内部value为null)可以走到orElse逻辑 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Jdk8Module()); UserDto result = objectMapper.readValue("{\"id\":\"1\", \"age\":30, \"name\":null}",UserDto.class); log.info("field name with null value dto:{} name:{}", result, result.getName().orElse("N/A")); //field name with null value dto:UserDto(id=1, name=Optional.empty, age=Optional[30]) name:N/A log.info("missing field name dto:{}",objectMapper.readValue("{\"id\":\"1\", \"age\":30}",UserDto.class)); // missing field name dto:UserDto(id=1, name=null, age=Optional[30])

    2
    15
  • 失火的夏天
    ConcurrentHashMap 的 Key 和 Value 都不能为 null,而 HashMap 却可以。 ConcurrentHashMap这个老爷子只说了value如果是空,会有二义性。就是在线程安全情况下,他到底是设置了一个null还是根本就没这玩意,key他老人家没说。。。老师可以说下理解吗? TreeMap的Key不能为空,因为TreeMap是基于compare的,空值不能compare。value可以为空,TreeMap并不线程安全。Hashtable 的 Key 和 Value也不能空,我想原理应该和ConcurrentHashMap一样。

    作者回复: Key也是一样的道理,此外,我也更同意他的观点,就是普通的Map允许null是否是一个正确的做法也是值得商榷的,会增加犯错的可能 一个更好的类库和框架并不是把最佳实践写在文档中,而是直接编码在实现中

    2
    11
  • Jerry Wu
    这是我个人的一些工作经历。 以前尝试过Optional,但其他人反馈看不懂,最后还是换回了if-else。 得出结论,技术要考虑团队的接受程度。 新技术、新特性虽好,但团队每个人的能力不同,而决定技术走向的,是团队最弱的那个人。

    作者回复: 也对

    16
    8
  • 美美
    有个规范我记得是说,不要在字段,方法参数,集合中使用Optional

    作者回复: 其实主要是序列化和反序列化的麻烦(兼容问题),现在使用spring boot + jackson已经没问题了,自动引入了Jdk8Module,如果你使用其他序列化框架的话可能要考虑一下这种方式是否适合!其实很多时候最佳实践呢也是需要随着变迁不断调整的。

    5
    6
  • 书林
    关于SQL 判断空有一点想提出来讨论,=NULL 不是赋值确实是判断,只是NULL和任何值的直接比较都为NULL,比如NULL<>NULL, NULL=NULL, NULL=1结果都为NULL。对 NULL 进行判断只能使用 IS NULL 或者 IS NOT NULL,或者ISNULL()。

    作者回复: 你说的没错,我改一下这句话 The NULL value can be surprising until you get used to it. Conceptually, NULL means “a missing unknown value” and it is treated somewhat differently from other values. You cannot use arithmetic comparison operators such as =, <, or <> to test for NULL. Because the result of any arithmetic comparison with NULL is also NULL, you cannot obtain any meaningful results from such comparisons.

    3
  • 大大大熊myeh
    原文:“归根结底,这是如下 5 个方面的问题:明确 DTO 种 null 的含义”或许是“明确 DTO各种 null 的含义”吧. MySQL中 count(1)选取每一行并赋值为1,进行统计 count(*)选取每一行进行统计 count(字段)选取每一行中的该字段,选择不为null的行进行统计 我认为Optional可以代替原来的if-else赋值,使代码看上去稍许简洁。但需要注意如Optional.ofNullable(number).orElse(0),当number为null时,返回的0不会赋值给number,它返回的是一个新地址(对象)。 思考题1,楼上Darren老兄说的很对。更加白话的说明“非并行映射中几乎无法容忍的歧义”就是——如果map.get(key)返回了null,无法明确是因为map中没有找到该key返回的null,还是因为该key包含的value就是null。 思考题2,xml配置文件中的if标签<if test="id!=null and id !=''"></if>需要注意if标签中的字段id如果是Date类型的话,不要写id!='',这是由于Data类型与字符串类型进行比较的报错,此时只需写null的判断即可。 以前不知道@Column注解的updateIfNull属性,学到了。

    作者回复: 明确 DTO 中 null 的含义,我们改一下

    3
  • 终结者999号
    在平常的开发中,对于DTO的值验证性校验也可以使用Hibernate Validator,也可以杜绝用户不按接口文档中所定义的格式输入,感觉也可以使用

    作者回复: 是

    3
  • Geek_fe5e8a
    老师讲的真的好 满分

    作者回复: 感谢

    1
  • 汝林外史
    问题大家都答的很好,我就直接问问题吧。 老师,Hashtable的put会对value做null判断,key是在调用hashcode方法时报空指针,而ConcurrentHashMap是直接对key和value做null判断,是不是Hashtable的设计有问题?

    作者回复: Hashtable虽然这种写法不怎么好看,但结果都是一个NullPointerException,作者可能觉得 int hash = key.hashCode(); 会抛空指针就没必要提前判断了吧

    1
收起评论
显示
设置
留言
26
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部