空指针异常是出现频率比较高的 bug,在出现空指针时,很多小伙伴都是习惯性地加一个 !=null 的判断,这个 bug 就解决了。

当代码中频繁出现 !=null 的判断时,我们就会很头疼,能不能高效、优雅地做这个判断?

答案当然是可以的。

第一步 ,当我们要做 !=null 的判断时,请停顿一下,看一下我们要做判断的这个数据是什么类型?

数据类型无非就是 String 字符串、Object/ 自定义对象、List 集合、Array 数组、Map 集合等类型。

第二步 ,思考这个数据类型对应的工具类有哪些?

String 类型,对应的工具类有 StringUtils。

Object 对象,对应的工具类有 ObjectUtils。

Array 数组,对应的工具类有 Arrays。

List 集合、Map 集合对应的工具类有 Collections、CollectionUtils 等。

这些工具类都是 Java、Spring 框架自带的工具类。

第三步 ,使用对应类型的工具类做判断。

1、如果这个数据是 String 类型时,请使用 StringUtils 工具类。

String str = "";
StringUtils.isEmpty(str);  // true

StringUtils 工具类比较有针对性,是针对 String 字符串的工具类。

public static boolean isEmpty(@Nullable Object str) {
   return str == null || "".equals(str);
}

在 isEmpty 方法中,既有为 null 的判断,也有是否等于空字符串的判断。

2、如果这个数据是 Object 类型,请使用 ObjectUtils 工具类。

Object obj = null;
ObjectUtils.isEmpty(obj); // true

3、如果这个数据是 Map 类型,也可以使用 ObjectUtils 工具类。

Map<String,Object> map = Collections.emptyMap();
ObjectUtils.isEmpty(map);// true

4、如果这个数据是 List 类型,还可以使用 ObjectUtils 工具类。

List<Integer> list =Collections.EMPTY_LIST;
ObjectUtils.isEmpty(list); // true

5、如果这个数据是数组类型,依旧可以使用 ObjectUtils 工具类。

// 数组
Object[] objArr = null;
ObjectUtils.isEmpty(objArr); // true

ObjectUtils 中的 isEmpty() 这一个方法,分别可以对字符串、数组、Map 集合、List 集合进行是否等于 null 的判断。

这个 isEmpty 方法为什么能判断这么多种数据类型呢?一起看下它的源码。

public static boolean isEmpty(@Nullable Object obj) {
    // 判断obj是否为null,如果是直接f
    if (obj == null) {
      return true;
    }
    // 判断obj是否是Optional的子类
    if (obj instanceof Optional) {
      // 如果是,则调用isPresent方法判断是否为null
      return !((Optional) obj).isPresent();
    }
    // 判断obj是否是CharSequence的子类
    if (obj instanceof CharSequence) {
      // 如果是,则获取长度,长度等于0时,就认为这个obj是空字符串
      return ((CharSequence) obj).length() == 0;
    }
    // 判断obj是否为数组
    if (obj.getClass().isArray()) {
      // 数组的长度等于0就认为这个数组是空数组
      return Array.getLength(obj) == 0;
    }
    // 判断obj是否为Collection集合的子类
    if (obj instanceof Collection) {
      // 用Collection子类的isEmpty方法判断集合是否为空
      return ((Collection) obj).isEmpty();
    }
    // 判断obj是否为Map接口的子类
    if (obj instanceof Map) {
      // 如果是,则进行强转,并用子类的isEmpty方法判断集合是否为空
      return ((Map) obj).isEmpty();
    }

    // else
    return false;
}

在这个静态方法中,首先对传入的 obj 对象进行是否等于 null 的判断,如果是则返回。如果不是 null,对 obj 进行数据类型进行定位,然后根据数据类型进行判断。

在这个方法中,封装了 Optional、CharSequence、Array、Collection、Map 数据类型,几乎涵盖所有的数据类型。

通过这段源码,我们也可以看出,它对复杂类型的集合的判断存在一些缺陷。也就是说它只判断了集合的长度,集合的长度为 0,就认为集合是空的。

// 创建一个只有一个元素的List集合
List<Integer> list = Collections.singletonList(null);
ObjectUtils.isEmpty(list); // false

上面的这段代码,我们创建了一个只有一个元素、且这个元素为 null 的 List 集合。

如果是对象数组,数组里有一个对象,但这一个对象为 null,这个判断就失灵了。

ObjectUtils 类中对对象数组的判断是另外一个 isEmpty 方法。

public static boolean isEmpty(@Nullable Object[] array) {
return array == null || array.length == 0;
}

因此在这 2 种情况下,我们再使用 ObjectUtils 的 isEmpty 方法就不合适了,我们需要对集合或数组里的每一个元素进行判断是否为 null。

6、针对 List 集合中元素是否为空的正确判断

Arrays.stream(list.toArray()).allMatch(ObjectUtils::isEmptyisNull);

这里用到了 Arrays 工具类。需要先把 List 集合转换成数组,然后再使用 Arrays 工具类对数组里的元素逐一判断是否为 null。

7、针对 Map 集合是否为空为 null 的判断

Map<String,Object> map = Collections.emptyMap();
CollectionUtils.isEmpty(map);

CollectionUtils 工具类中 isEmpty 判断方法源码:

public static boolean isEmpty(@Nullable Map<?, ?> map) {
    return map == null || map.isEmpty();
}

map 是否等于 null,map 集合是否为空,这一个方法中聚合了两个判断。我们直接调用它就可以减少我们的工作量。

除此之外,CollectionUtils 工具类中还有针对 List 集合的 isEmpty 方法:

List<Integer> list = null;
// 使用CollectionUtils工具类判断list集合是否为空
CollectionUtils.isEmpty(list); // true

针对 List 集合的 isEmpty 源码:

public static boolean isEmpty(@Nullable Collection<?> collection) {
    return collection == null || collection.isEmpty();
}

在这个方法中,既有为 null 的判断,也有 isEmpty 的判断,聚合了两个判断,我们直接调用它也可以减少我们的工作量。

总结

判断一个数据是否为 null,可以经过三步,第一步思考属于什么数据类型,第二步根据数据类型选择正确的工具类,第三步,使用正确的工具类进行判断。

针对 String 字符串类型的数据,直接使用 StringUtils 工具类。

针对除了 String 之外的数据类型都可以使用 ObjectUtils 工具类。

针对 List 集合、Map 集合,除了 ObjectUtils 还可以使用 CollectionUtils 工具类进行判断。

针对数组和 List 集合中的子元素是否都为 null 的判断,则需要遍历