今天不是介绍 name.equals(“ali”) 改成 “ali”.equals(name)等技巧,我个人并不喜欢这些技巧,因为它只解决了当前这句代码不会抛出空指针,后续用到name的地方还是可能抛出空指针,没有从根本上解决问题(其实我更建议使用第一种,如果有空指针,快速失败,而不是模棱两可的继续执行下去)
今天不讨论局部避免空指针的技巧,而是讨论如何使用一些方法和原则从根源上解决空指针问题
public void test(Integer id, List<Integer> counts){ for (Integer count : counts) { // 参数counts 可能为null System.out.println(count.toString()); // 集合中的元素可能为null } String sid = id.toString(); // 参数id可能为null Customer customer = getCustomerByID(sid); // 函数返回值肯能为null System.out.println(customer.getName().length()); // 对象中的属性可能为null }
解决方案:
约定函数的参数一定不能为null,否则函数的行为不可预测,由调用者去保证参数的合法性
关于集合为空(list.size()==0)的问题,我觉得也应该放在调用方判断,因为从业务上来说跟null没什么区别。
解决方案: - 如果是不信任的函数(应用间接口,某些二方库函数),将返回值用Optional包装 - 应用内部函数返回一定不能为null,如果可能为null,则用Optiona包装类型
解决方案:
推荐使用禁止存放null的集合,将判断空元素的过程前移到数据生成时
没找到合适的类库(大家有知道的可以推荐下哈),所以之前自己写了一个(仅供参考) https://github.com/fangqiang/safe-container
解决方案: 1. 规范字段定义。对象中每个字段只能有2种状态 1. 字段如果在整个生命周期中可能为null,那定义时就用Optional包装类型 2. 非Optional包装字段,表明该字段在实例化后就一直不为null
核心原则就是让每个变量从创建的那一刻起就不是null,这样无论函数之间怎样调用来调用去都不会出现null。如何做到每个变量创建时就不是null?
/** * 最后给个例子说明契约式编程的的简洁 */ /** * 防御式编程 */ public Integer get(Integer percent, List<Integer> counts){ if(percent == null || counts == null){ return null; } if(counts.size() == 0){ return null; } if(percent < 0){ throw new RuntimeException(); } int sum=0; for (Integer count : counts) { if(count != null){ sum += count; } } return sum; } /** * 契约式编程 * * @param percent percent 必须大于0,否则抛出异常 * @param counts @NotEmpty注解提示调用方先判断size() > 0 * NoneNulList保证了容器中的元素不可能为null,将这判空这个过程前移到数据产生时 * @return */ public Integer get2(Integer percent, @NotEmpty NoneNulList<Integer> counts){ // 业务相关的校验还是由本函数完成 if(percent < 0){ throw new RuntimeException(); } int sum=0; for (Integer count : counts) { sum += count; } return sum; }