Open superleeyom opened 3 years ago
把阿里巴巴的《Java开发手册》里,关于 NPE异常总结了下,预防空指针异常,从你我做起!
【强制】所有的 POJO 类属性必须使用包装数据类型。
数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
【强制】在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。
List<Pair<String, double>> pairArrayList = new ArrayList<>(2); pairArrayList.add(new Pair<>("version1", 8.3)); pairArrayList.add(new Pair<>("version2", null)); // 抛出 NullPointerException 异常 pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue));
因为 HashMap 的 merge 方法里,若 value 为 null,则会抛 NPE:
@Override public V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) { if (value == null) // 抛出 NullPointerException 异常 throw new NullPointerException(); //.... }
这个问题java9已经修复,所以也可以尝试升级jdk,或者把value为null的过滤掉就行。另外注意:如果key相同也会抛异常 ,改成如下代码相同的key就不会报异常,新key的value会替换旧key的value:
java9
pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue,(v1, v2) -> v2))
【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一 致、长度为 0 的空数组。
List<String> list = new ArrayList<>(2); list.add("guan"); list.add("bao"); // array 的值为:["guan","bao",null,null] String[] array = list.toArray(new String[4]);
如果循环遍历 array 的时候,不注意,就有可能抛 NPE:
for (String s : array) { // 抛出 NullPointerException 异常 System.out.println(s.length()); }
所以建议数组空间大小的 length 设置为0,动态创建与 list size 相同的数组,性能最好。
String[] array = list.toArray(new String[0]);
【强制】在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行 NPE 判断。
List<String> list = new ArrayList<>(2); list.add("guan"); list.add("bao"); List<String> listSecond = null; // 抛出 NullPointerException 异常 list.addAll(listSecond);
观察 ArrayList 的 addAll 方法的源码:
public Boolean addAll(Collection<? extends E> c) { // 若c为null,这里会抛NPE Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
所以正确的做法是,对输入参数做判空化处理:
// CollUtil.emptyIfNull(List<T> set)方法是 hutool里面的对集合null设置为empty的方法,方法内实际为: // (null == set) ? Collections.emptyList() : set list.addAll(CollUtil.emptyIfNull(listSecond));
【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况。
【强制】三目运算符(condition? 表达式 1 : 表达式 2) 中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常。
Integer a = 1; Integer b = 2; Integer c = null; Boolean flag = false; // a*b的结果是int类型,那么c会强制拆箱成int类型,抛出NPE异常 Integer result = (flag ? a * b : c);
a*b 包含了算术运算,因此会触发自动拆箱过程(会调用 intValue 方法),此时a*b的结果是 int 类型,那么c会强制拆箱成 int 类型,以下两种场景会触发类型对齐的拆箱操作:
a*b
表达式 1 或表达式 2 的值只要有一个是原始类型。
表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
【强制】当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。
可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;
SELECT IFNULL(SUM(column), 0) FROM table;
【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说 明什么情况下会返回 null 值。
防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。
【强制】当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断。
public static void method(String param) { switch(param) { case "sth": System.out.println("it's sth"); break; case "null": System.out.println("it's null"); break; default: System.out.println("default"); } } public static void main(String[] args) { // 会报NPE异常 method(null); }
【强制】字符串判断相等,常量一定要写在前面「这个手册上没有,我自己加的,哈哈」
String str1 = null; String str2 = "hello world"; // 错误的写法,会抛NPE str1.equals(str2); // 正确写法 str2.equals(str1);
其他的可能产生 NPE 的场景:
public int f() { Integer a = null; // 抛出NPE异常 return a; }
另外这种情况也会自动拆箱产生NPE:
Integer num = null; // 抛出NPE异常 if(num > 1) { // do someing }
// 数据库查询用户信息 User user = userMapper.getById(123); // user可能为null,抛出NPE异常 String userName = user.getUserName();
ApiResponse<UserInfoDTO> apiResponse = userFeign.getById(123); // user可能为null User user = apiResponse.getData(); if(user!=null){ String userName = user.getUserName(); }
对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
obj.getA().getB().getC();
集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null,因为 List 里面也可以存 null。
可以考虑使用 JDK8 的 Optional 类来防止 NPE 问题。
把阿里巴巴的《Java开发手册》里,关于 NPE异常总结了下,预防空指针异常,从你我做起!
数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
因为 HashMap 的 merge 方法里,若 value 为 null,则会抛 NPE:
这个问题
java9
已经修复,所以也可以尝试升级jdk,或者把value为null的过滤掉就行。另外注意:如果key相同也会抛异常 ,改成如下代码相同的key就不会报异常,新key的value会替换旧key的value:如果循环遍历 array 的时候,不注意,就有可能抛 NPE:
所以建议数组空间大小的 length 设置为0,动态创建与 list size 相同的数组,性能最好。
观察 ArrayList 的 addAll 方法的源码:
所以正确的做法是,对输入参数做判空化处理:
a*b
包含了算术运算,因此会触发自动拆箱过程(会调用 intValue 方法),此时a*b
的结果是 int 类型,那么c会强制拆箱成 int 类型,以下两种场景会触发类型对齐的拆箱操作:表达式 1 或表达式 2 的值只要有一个是原始类型。
表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
可以使用如下方式来避免 sum 的 NPE 问题:
SELECT IFNULL(SUM(column), 0) FROM table;
防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。
其他的可能产生 NPE 的场景:
另外这种情况也会自动拆箱产生NPE:
对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
级联调用
obj.getA().getB().getC();
一连串调用,易产生 NPE。集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null,因为 List 里面也可以存 null。
可以考虑使用 JDK8 的 Optional 类来防止 NPE 问题。