你是不是也有过这种经历?写 Java 代码的时候顺风顺水,敲键盘的节奏都跟着音乐走,结果一跑起来突然蹦个 NullPointerException(简称 NPE),红通通的报错信息跳出来,你盯着屏幕半天:“我明明给这个对象赋值了啊,咋就空了?” 尤其是赶项目的时候,本来就急着上线,这一下直接卡壳,别提多闹心了!
其实 NPE 绝对是 Java 程序员的 “老朋友” 了,不管你是刚入门的新手,还是工作三五年的老鸟,都大概率栽过它的坑。小索奇之前就踩过一个巨冤的坑:写用户登录逻辑的时候,调用了一个工具类获取用户 IP,心想 “用户只要访问,肯定有 IP 啊”,结果测试的时候用模拟请求没传 IP,工具类直接返回 null,我后面直接用 ip.getCity (),得,NPE 立马找上门,还被测试小姐姐调侃 “哥,你这代码不防‘空’啊”,当时真想找个地缝钻进去。
那为啥 NPE 这么容易出现呢?说穿了就是咱们 “想当然” 了 —— 总觉得某个对象肯定有值,没做任何判断就直接调用它的方法或属性。我总结了三个最常见的坑点,你看看是不是中过招?
第一个坑:接口返回值没处理。现在开发谁还不调几个第三方接口啊?比如你调支付平台的接口查订单状态,对方文档写着 “正常返回订单对象”,你就信了,直接用 order.getAmount () 计算金额。结果有次对方系统出问题,返回个 null,你的代码瞬间崩了。这时候你再去找接口文档,才发现最下面一行小字写着 “异常情况下返回 null”—— 这就很坑了,但怪谁呢?还不是咱们自己没做判空处理。
第二个坑:集合操作不注意。比如你从数据库查了一个用户列表,想遍历取每个用户的手机号,代码写的是 “for (User user : userList) { String phone = user.getPhone (); }”。看起来没问题吧?但如果数据库里有个用户的 phone 字段是 null,或者 userList 本身就是 null(比如查询条件错了,没查到数据),一跑起来 NPE 就来了。小索奇之前就遇到过,测试的时候数据都是好的,上线后有个老用户的手机号没录,直接报错,查了半天才发现是集合里的元素有 null。
第三个坑:方法参数没校验。比如你写了一个方法 “public void updateUser (User user) { user.setAge (user.getAge () + 1); }”,别人调用的时候传了个 null 进来,你这方法里直接操作 user,肯定报 NPE。这时候你可能会说 “调用者怎么不传个正常的对象?” 但作为开发者,咱们不能假设别人会正确使用你的方法,对吧?最好在方法开头加个校验,比如 “if (user == null) { throw new IllegalArgumentException ("用户对象不能为 null"); }”,这样至少能提前报错,还能告诉调用者问题出在哪,比直接抛 NPE 友好多了。
那怎么避免这些坑呢?小索奇分享几个亲测有效的办法,你可以试试。
首先,最简单也最基础的:养成 “先判空再使用” 的习惯。别嫌麻烦,比如拿到一个对象后,先判断 “if (obj != null)”,再调用它的方法。有人觉得这样写代码太啰嗦,满屏都是 if 判空,但你想想看,比起上线后崩了再熬夜改 bug,多写几行判空是不是划算多了?比如处理接口返回值的时候,写成 “User user = api.getUserById (id); if (user != null) { // 做业务逻辑 } else { // 处理 null 的情况,比如返回提示 }”,这样就安全多了。
然后,Java 8 之后不是有个 Optional 类吗?这个东西就是专门用来对付 null 的。简单说就是 Optional 帮你把对象包了一层,你得先问它 “里面有东西吗”,有才能拿出来用,没有就走别的逻辑,从根源上避免直接碰 null。比如刚才的例子,用 Optional 可以写成 “Optional userOpt = Optional.ofNullable (api.getUserById (id)); userOpt.ifPresent (user -> { // 做业务逻辑 });”,这样就不用写显式的 if 判空了,代码还更简洁。不过有个小提醒:别把 Optional 当参数或者返回值到处传,它主要是用来包装可能为 null 的对象,不是让你用它替代所有对象的,不然反而会把代码搞复杂。
还有个小技巧:用 Objects.requireNonNull 做校验。这个方法是 Java 7 加的,作用就是如果传入的对象是 null,就直接抛 NullPointerException,还能自定义报错信息。比如在方法开头写 “Objects.requireNonNull (user, "用户对象不能为 null");”,比自己写 if 判空然后抛异常省事多了,而且报错信息更明确,方便排查问题。小索奇现在写方法的时候,只要参数是对象类型,基本都会加这个校验,真的能避免很多潜在问题。
对了,还有个误区我必须提一嘴:有人觉得 “判空太麻烦,我写个 try-catch 把可能报 NPE 的代码包起来不就完了?” 但你想啊,try-catch 是用来处理异常的,不是预防异常的!万一真的出现 null,程序虽然不会直接崩,但逻辑已经错了 —— 比如你想给用户加积分,结果因为 NPE 没加上,用户找过来投诉,你还不知道问题在哪,这不是治标不治本嘛?而且滥用 try-catch 会影响性能,还会让代码变得混乱,真的不推荐这么做。
你有没有踩过 NPE 的坑?比如在循环里取集合元素没判空,或者调用链式方法(比如 a.getB ().getC ())的时候中间某个对象是 null,直接报错?评论区说说你的经历,咱们一起避坑~小索奇敢说,只要养成判空的习惯,用好 Optional 和 Objects.requireNonNull,至少能减少 80% 的 NPE 问题。
我是【即兴小索奇】,点击关注,后台回复 领取,获取更多相关资源