?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辅助检查