?What 什么是空指针

空指针是运行时异常,所以就导致在编码时不易发现,在运行时才会暴露
因为Java中对象可以为null,当去使用为null的对象操作时会抛出空指针

NullPointerException 官方解释?(后文用NPE代替NullPointerException)

NullPointerException is a RuntimeException. In Java, a special null value can be assigned to an object reference. NullPointerException is thrown when an application attempts to use an object reference that has the null value. These include:

  • Calling the instance method of a null object.
  • Accessing or modifying the field of a null object.
  • Taking the length of null as if it were an array.
  • Accessing or modifying the slots of null as if it were an array.
  • Throwing null as if it were a Throwable value.

?Why 为什么要避免

公司内部 CaseStudy 平台有很多空指针引起的事故,其中一案例涉及金额为180万元,退款订单数1600+,客诉50+,难以想象一行代码可以造成这么大的影响……

空指针可以说是编程中出现最频繁的bug,据统计70%以上的异常都是源于NPE

如果编码不小心,可能会造成很大的损失,但只要养成好的编码习惯,NPE就不会出现

?How 消灭NPE

1、已知对象放前

总是从已知的非空String对象中调用equals()方法,equals()方法是对称的,a.equals(b)与b.equals(a)是完全相同的,这是避免空指针的最常见的技巧

//错误方式 ❌if(ObjectA.equals(“ObjectB”)){    //do something}//正确方式 ✅if(”ObjectB“.equals(ObjectA)){    //do something}//正确方式 ✅Boolean.TRUE.equals(a);
2、对象判空
Objects.equals(obj1,obj2);------------public static boolean equals(Object a, Object b) {    return (a == b) || (a != null && a.equals(b));}
3、使用contains()/containsKey()时注意
HashMap map = null;//错误示范 ❌Object test = map.get(“test”);//错误示范 ❌if (map.containsKey(“test”)){  System.out.println(map.get(“test”));}
4、使用 concat()/trim()时注意
String str = null;//错误示范 ❌str.concat(“hello”);//错误示范 ❌str.trim();
5、valueOf()与toString()返回相同结果时,使用前者

调用null对象的toString()会抛出空指针异常,而使用valueOf()可以获得相同的值,传递一个null给valueOf()将会返回null,Integer,Double,BigDecimal

String s = null;//错误方式 ❌System.out.println(s.toString());//正确方式 ✅System.out.println(String.valueOf(s));---------------public static String valueOf(Object obj) {  return (obj == null) ? “null” : obj.toString();}
6、不必要的自动包装和自动解包

如果wrapper类对象是null,自动包装同样容易导致空指针异常,如int 与Integer类型

public class Student {    private Integer age;    private String name;    public Student(String name) {        this.name = name;    }    //setter and getter}Student student = new Student("ram");// NullPointerExceptionint age = student.getAge();// 返回nullInteger age = student.getAge();
7、使用工具类StringUtils,包括对null对象的判断
System.out.print(StringUtils.isEmpty(null)); //trueSystem.out.print(StringUtils.isBlank(null)); //trueSystem.out.print(StringUtils.isNumeric(null)); //falseSystem.out.print(StringUtils.isAllUpperCase(null)); //false
8、先创建空集合

通过返回一个空的集合或者空数组,避免返回的是空集合而不是null,在确保调用size(),length()的时候不会因为返回的是null而报NPE

Map emptyMap = Collections.EMPTY_MAP;Set emptySet = Collections.EMPTY_SET;List emptyList = Collections.EMPTY_LIST;
9、集合正确判空方式

Apache Commons Lang 包中CollectionUtils类可以帮助快速判空,集合可能为空

//错误方式 ❌if(list.size() > 0){}//正确方式 ✅if(CollectionUtils.isEmpty(list)){  //do something}---------------public static boolean isEmpty(Collection coll) {  return (coll == null || coll.isEmpty());}
10、对遍历的数组和集合进行判空

集合和数组可能为null,在进行遍历前先进行判空处理

Collection myNumbers = buildNumbers();//正确方式 ✅if (CollectionUtils.isNotEmpty(myNumbers)) {    for (Integer myNumber : myNumbers) {        System.out.println(myNumber);     }  }
11、Optional

一连串调用如obj.getA().getB().getC() ,容易出现NPE,且通过下面格式判断的话影响代码可读性,可使用Java 8 中Optional,感兴趣的同学可自行搜索

if (user != null) {     Address address = user.getAddress();     if (address != null) {         Country country = address.getCountry();         if (country != null) {            String isocode = country.getIsocode();            if (isocode != null) {                 isocode = isocode.toUpperCase();             }         }     }}

Optional目的是即保证程序健壮性的同时,又保持代码的优雅和可读性

String str= Optional.ofNullable(getMsgFormDB())              .map(d->d.trim())              .map(d->d.replace(getMsgFromWebService()))              .map(d->d.trim())              .orElseGet(() -> Strings.EMPTY);
12、监控报警

最后一点不是从代码层面上去"消灭",但监控报警是防止空指针造成影响的最后一道防线,是为了及时发现问题,一般设置监控指标NPE > 1 ,就应该引起报警且报警级别高。

?小结:

  • 写代码时思考清楚使用的对象是否可能为空
  • 写完代码后再认真检查一遍
  • 使用插件如 Alibaba Java Coding Guidelines 、FindBugs辅助检查