写 Java 的时候,是不是经常遇到这种崩溃瞬间:代码本地跑得好好的,一上线突然报 NullPointerException,日志翻了半天,才发现是某个对象没初始化就调用方法了?空指针这东西,看着简单,却是 Java 开发里最容易踩的坑之一,小索奇见过不少开发者因为没处理好空指针,导致线上服务频繁报错,加班到半夜排查问题。

其实很多空指针问题,都不是 “不可控” 的,而是咱们写代码时忽略了一些细节。今天就跟你掰扯掰扯,Java 里最容易触发空指针的 4 个场景,以及怎么从根源上避开它们 —— 都是小索奇和身边同事踩过的坑,亲测实用。

第一个场景:调用对象方法 / 属性前,没判空。比如你写了个获取用户姓名的方法,参数是 User 对象,直接 return user.getName ()。如果调用这个方法时,传进来的 User 是 null,那必然会报空指针。你可能会说 “我传的肯定是有值的”,但实际开发中,用户数据可能从数据库查不到,或者接口返回的就是 null,这种情况太常见了。

怎么解决?最简单的就是提前判空,比如用 if (user != null) { return user.getName (); } else { return ""; }。但如果每个对象都这么写,代码会特别啰嗦。小索奇建议用 Java 8 之后的 Optional 类,比如 Optional.ofNullable (user).map (User::getName).orElse (""),一行代码就能搞定,既简洁又能避免空指针。而且 Optional 还能避免 “嵌套判空” 的问题,比如要获取 user.getAddress ().getCity (),如果用传统判空,得写三层 if,用 Optional 的话,Optional.ofNullable (user).map (User::getAddress).map (Address::getCity).orElse ("") 就行,是不是清爽多了?

第二个场景:集合遍历前,没检查是否为空。比如你从接口拿到一个 List,直接用 for 循环遍历,或者用 stream 处理。如果这个 List 是 null(不是空集合,是空对象),遍历的时候就会报空指针。很多人会把 “null” 和 “空集合” 搞混,觉得 “接口返回空的话,应该是个空 List 啊”,但实际情况是,有些接口设计不规范,返回的就是 null。

小索奇的习惯是,拿到集合后先做 “空安全处理”。比如用 CollectionUtils.isEmpty (list)(需要导入 commons-collections 包),这个方法会同时判断 list 是不是 null 和是不是空集合;如果不想导包,也可以自己写个工具方法,比如 public static boolean isEmpty(Collection coll) {return coll == null || coll.isEmpty (); }。另外,返回集合的时候,尽量返回空集合而不是 null,比如方法里如果查询不到数据,别 return null,而是 return Collections.emptyList (),这样调用方就不用额外判空了。

第三个场景:字符串比较时,用 “对象.equals (常量)”。比如你要判断用户状态是不是 “ACTIVE”,写了 userStatus.equals ("ACTIVE")。如果 userStatus 是 null,这行代码就会报空指针。但如果把顺序反过来,写成 "ACTIVE".equals (userStatus),就算 userStatus 是 null,也只会返回 false,不会报错 —— 因为常量 “ACTIVE” 肯定不是 null,调用它的 equals 方法绝对安全。

这个小细节,很多新手容易忽略,小索奇刚开始写 Java 的时候也踩过这个坑。后来养成习惯,只要是字符串和常量比较,都把常量放前面。另外,如果用的是 Java 7 及以上,也可以用 Objects.equals (userStatus, "ACTIVE"),这个方法会先判断两个参数是不是都为 null(都为 null 返回 true),再判断其中一个是不是 null(一个为 null 返回 false),最后调用 equals 方法,也是个空安全的写法。

第四个场景:自动拆箱时,包装类为 null。比如你定义了 Integer age = null,然后写 int a = age; 这时候就会触发自动拆箱,把 Integer 转成 int,但因为 age 是 null,就会报空指针。这种情况在处理数据库查询结果时特别常见,比如数据库里某个字段允许为 null,用 MyBatis 查询时,如果字段值是 null,对应的 Integer 类型变量就是 null,这时候如果直接赋值给 int 变量,就会出问题。

怎么避免?小索奇建议,除非确定包装类一定有值,否则不要随便自动拆箱。如果需要用基本类型,可以先判空,比如 int a = (age != null) ? age : 0;。另外,在定义变量时,能给默认值就给默认值,比如 Integer age = 0,而不是 Integer age = null,从源头减少 null 的出现。

其实空指针问题,本质上是 “对 null 值的处理意识不够”。小索奇认为,写 Java 代码时,要时刻提醒自己:这个对象会不会是 null?这个集合会不会是 null?这个包装类会不会是 null?提前做好防护,比出了问题再排查要省太多事。

你平时写 Java 的时候,还遇到过哪些奇葩的空指针场景?有没有自己的避坑小技巧?评论区分享一下,咱们一起把代码写得更稳,少加加班~

我是【即兴小索奇】,点击关注,后台回复 领取,获取更多相关资源

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐