Mark Xu 的博客

记录精彩的程序人生

避免空指针和下标越界的方案总结

最近处理了一批公司项目的线上 bug,其中以空指针异常和下标越界占大多数,本文结合项目中遇到的各种空指针和越界问题,总结了产生的原因,及如何编码才能更大程度上减少空指针和下标越界的发生。

空指针

空指针异常出现的原因

可能的原因有如下几个:

  • 对象没有初始化就进行操作
  • 对象已经初始化过,但是被回收或者手动置为 null
  • 进程被杀死后静态变量没有初始化
  • API 返回脏数据,解析出的对象状态异常

有效避免空指针异常的编码习惯

1、从已知的 String 对象中调用 equals 方法和 equalsIgnoreCase 方法,而非未知对象

Object unknownObject = null;

//错误方式 – 可能导致 NullPointerException
if(unknownObject.equals("knownObject")){
   // do something
}
 
//正确方式 - 即便 unknownObject 是 null 也能避免 NullPointerException
if("knownObject".equals(unknownObject)){
    // do something
}

2、优先使用 valueOf,而不是 toString

BigDecimal bd = getPrice(); // 假设 bd 为 null
System.out.println(String.valueOf(bd)); // 不会抛出空指针异常
System.out.println(bd.toString()); // 抛出 "Exception in thread "main" java.lang.NullPointerException"

3、避免从方法中返回 null 对象,而是返回空 Collection 或者空数组

public List getOrders(Customer customer){
    List result = Collections.EMPTY_LIST;
    return result;
}

Collections 类还提供了方便的空 Set 和空 Map:Collections.EMPTY_SET、Collections.EMPTY_MAP

4、善于使用 @NonNull 和 @Nullable 注解

@NonNull
private int getAge(@NonNull Student name) {
    // get age
}

当调用 getAge 方法时,编译器或者 IDE 会在参数可能出现 null 的地方提示我们

5、避免代码中不必要的自动包装和自动拆包

Person ram = new Person("ram");
// 如果 ram 对象没有电话号码时,下面的代码会导致空指针异常
int phone = ram.getPhone();

6、为对象定义合理的默认值

大部分空指针异常的出现是因为使用不完整的信息创建对象或者未提供所有的依赖项,创建对象时,为没有定义的属性提供合理的默认值,可以有效避免空指针异常。

// 对集合类型的变量,可以直接初始化
private List<String> studentList = new ArrayList<>();

8、在数据库中定义字段是否可为空

在数据库中维护 null 约束可以有效减少 Java 代码中的空指针检查。

9、使用空对象模式

声明一个 NullObject 类,与目标对象实现相同的接口(或继承同一个父类),这样客户端调用时可以像调用目标对象一样调用空对象的方法,在空对象的实现方法中可以做出相应的提示

空对象模式并不推荐使用,参考译文:我们为什么爱你 --null(3. 空对象模式)

10、由网络层来处理 API 脏数据(可以参考文末链接中美团外卖的处理方式)

普通的网络请求写法是,在请求成功的回调中对 json 进行解析,如果服务端回给我们的数据包含脏数据时,解析出来的对象就会出现异常,获取某些属性时可能会出现空指针情况,而异常的数据会直接反馈到 UI 层。我们可以考虑将脏数据的处理给到网络层来做,保证 UI 层拿到的数据都是完整的数据。

空指针异常的处理方式

对于空指针异常的处理,其实涉及到项目的错误处理机制,有些主张不做判断,直接抛出,不能容忍 null 参数;有些主张对 null 进行判断,分类处理。

  • 分析 null 出现的原因,减少出现概率
  • 减少 null 判断,优化代码嵌套层次,对于不可能出现的 null��使用断言
  • 不随意吃掉异常,隐藏真正的问题,要去分析本质原因,对症下药
  • 对于无法处理的空指针异常,暂时进行 try catch 处理(如三方库内部的异常)
  • 结合上面的编码推荐,防患于未然

数组越界

数组越界出现的情形

1、调用三方库方法,参数包含集合或数组时,三方库没对集合或数组进行判断

2、String 对象为 "" 时,未进行长度判断

3、ListView 中,外部持有 Adapter 里数据的引用,外部对数据进行更改,且没及时调用 notifyDataSetChanged 时

4、多线程下对容器进行操作

避免数组越界出现的编码习惯

1、调用三方库方法,传递集合时,进行集合大小判断

2、使用 String 对象前,使用 TextUtil 的 isEmpty 方法对 String 的引用及长度进行判断

参考文档

java 如不想么每次都判空 if(o !=null) 怎么做?

避免 Java 应用中 NullPointerException 的技巧和最佳实践

美团外卖 Android Crash 治理之路

留下你的脚步