1. 自动拆箱出现 null
包装器类型自动拆箱为基础类型时极容易出现NPE。如下图示例,方法void initTask(int taskId),调用时taskId如果为 null,则会出现NPE。正确做法是 可能为 null 的属性,一律声明为包装器,此外从外部获取的变量一定要检查 null,进行防御式编程。
2. 遍历集合 出现 null
集合List支持 foreach 遍历,但是如果List变量为null,则一定会发生空指针异常 NPE。如下代码所示,当ids为空时,会发生NPE。
正确做法,在遍历List之前,一定要进行空值检查。
List<Integer> ids = extractFromRequest();
//如果ids为 null,则发生 NPE
for(Integer id: ids){
//do something
}
3. 集合数组中出现 null
此外集合 List 中对象可能为空,在遍历集合时,要检查集合中的对象是否为 Null,否则可能发生 NPE。
List<Integer> ids = extractFromRequest();
//如果ids为 null,则发生 NPE
for(Integer id: ids){
// id可能为 null,则会发生 NPE
System.out.println(id.toString());
}
4. Optional.of() 出现 null
Optional 是 Java 8 提供的一个工具类,它可以以更加优雅的方式来处理空值。在使用 Optional 类的时候,有两种初始化方式,分别是 Optional.of(object) 和 Optional.ofNullable(object)。需要注意的是,在使用 Optional.of(object) 的时候,如果 object 为 null,就会发生空指针异常NPE。
正确做法:使用 Optional.ofNullable();
5. Stream和 Lamada中出现 null
若在lambda表达式中出现了Null,就可能会发生空指针异常 NPE。具体示例如下图所示,当使用Stream.map方式进行映射时,可能会导致返回值为Null。
正确做法:filter(Objects::nonNull) 过滤为 Null 对象
6. json 解析出现 null
使用 fastjson 可能遇到解析的对象为 null 的情况。很多业务系统使用 json 存储扩展字段,一般情况下mysql 字段默认值为""或 null,这种情况使用 FastJson 解析时,就会解析出 null对象,不判空就会出现空指针异常。
如下代码所示,当传入的json 字符串为空值""时,解析出的 json 对象为 Null。
7. 打印日志使用 + 号出现 null
日志打印时,很多人习惯使用 + 加号拼接日志,这种情况可能导致 空指针异常。
正确做法:使用{}占位符方式,打印变量。 日志框架会进行判空,当变量出现 Null 时,不会出现空指针。
很多人在 review 代码时,不重视日志代码,容易忽略日志代码中的问题。曾经有个同事搞出的线上问题就是因为日志打印出现了 NPE。
一定要敬畏每一行代码,包括日志代码。
8. 返回值异常处理
如下所示,业务系统在调用 Rpc 后,会对结果判空和检查异常码。如果调用失败,则会上报异常码。然而这两者不能完全放在一起,因为当 result 为 null 时,上报异常码会导致空指针异常的发生。
RpcResult result = invokeRpcMethod();
if(result==null || result.getCode!=SUCCESS){
log.error("调用失败 result:{}", result);
//当 result 为空时,则一定发生空指针。
reportCode(result.getCode());
}
一、 对象判空
if (obj != null) { // 进行对象非空判断 }
Object obj = null; // 或者 obj = new Object(); if (obj == null) { // 对象为空 } 另外,Guava 库还提供了一个更方便的方法,使用方式如下 import com.google.common.base.Objects;
if (Objects.isNull(obj)) { // 对象为空 }
二、 字符串判空
List list = new ArrayList<>(); if (list == null || list.size() == 0) { // list 为空 } 简单,但需要写太多代码判断,会显得有些麻烦。或 使用 Apache Commons Lang3 提供的 判断字符串是否为空或者只包含空格字符。这种方式使用起来非常方便: import org.apache.commons.lang3.StringUtils;
if (StringUtils.isBlank(str)) {
// 字符串为空
}
还可采用 Java 8 引入的 String#isEmpty() 方法判断字符串是否为空字符串:
if (str == null || str.isEmpty()) {
// 字符串为空
}
还有 Java 11 引入的 String#isBlank() 方法判断字符串是否为空或者只包含空格字符:
if (str == null || str.isBlank()) {
// 字符串为空
}
若还要 判断字符串是否为空或null:
String str = null; // 或者 str = "";
if (str == null || str.length() == 0 || str.trim().length() == 0) {
// 字符串为空或null
}
也可以使用 Apache Commons Lang3 库中的 StringUtils 工具类中的 isBlank() 方法进行判断:
import org.apache.commons.lang3.StringUtils;
if (StringUtils.isBlank(str)) { // 字符串为空或null或只包含空白字符 } 直接使用 == 判断是否为 null,或使用 equals() 判断是否等于空字符串 "": String str = null; if (str == null || str.equals("")) { // 字符串为空 } 使用 isEmpty() 方法判断字符串是否为空字符串 "": String str = ""; if (str.isEmpty()) { // 字符串为空 } 使用 isBlank() 方法判断字符串是否为空或全是空格: String str = " "; if (StringUtils.isBlank(str)) { // 字符串为空或全是空格 } 使用正则表达式判断字符串是否为空或只包含空格: String str = " "; if (str.matches("\s*")) { // 字符串为空或只包含空格 } 需要注意的是,第三种方法需要导入 StringUtils 类,而第四种方法虽然不需要导入任何类,但比较麻烦,建议使用前三种方法中的一种。 三、 List 判空
使用 if (list == null || list.isEmpty()) 判断 List 是否为 null 或者是否为空列表。或使用 Java 8 引入的 isEmpty() 方法判断 List 是否为 null 或者是否为空列表:但注意 isEmpty() 方法只会判断集合是否为空,而不会判断集合是否为 null。因此,使用 Collection#isEmpty() 方法之前,要先判断集合是否为 null。如果为 null,则该方法会抛出空指针异常。正确的使用方法如下:
List list = ...; if (list != null && !list.isEmpty()) { // 先判断 list 是否为 null,再判断 list 是否为空 // 进行操作 }
if (list == null || list.isEmpty()) {
// List 为空
}
上述代码中,先使用 list!=null 来判断 list 是否为 null,如果是,则不会进入条件语句中。如果不是,则使用 !list.isEmpty() 来判断 list 是否为空,如果不为空,则进入条件语句中进行操作。
## 四、Map 判空
Map<String, String> map = null; // 或者 map = new HashMap<>();
if (map == null || map.isEmpty()) {
// Map为空或null
}
private void putInfoToMap(Map<String,String> map ,String key ,String value){
if(StringUtils.isEmpty(key)){
return;
}
if(StringUtils.isEmpty(value)){
map.put(key, " ");
}else{
map.put(key, value);
}
}
## 五、数组判空
String[] array = null; // 或者 array = new String[10];
if (array == null || array.length == 0) {
// 数组为空或null
}
六、Set判空
Set<String> set = null; // 或者 set = new HashSet<>();
if (set == null || set.isEmpty()) { // Set为空或null } 在判断集合、数组、对象是否为空时,进行非空判断时最好先判断对象是否存在,否则在对象不存在的情况下操作对象可能会导致空指针异常的发生。 ## 七、文件判空 public SalaryExcelOperatVo uploadExcel(MultipartFile multipartFile) {
if (multipartFile==null) {
log.error("文件不能为空");
throw new RuntimeException("上传Excel文件内容为空,请重新上传!");
}
String fileName = multipartFile.getOriginalFilename();
//判断文件是否是excel文件
assert fileName != null;
if (!fileName.endsWith("xls") && !fileName.endsWith("xlsx")) {
log.error(fileName + "不是Excel文件!");
throw new RuntimeException(fileName + "不是Excel文件!");
}
//保存文件到本地
File dir1 = new File("/roots/uploadFile/xzExcel");
if (!dir1.exists()) {
dir1.mkdirs();
}
//统一日期格式
LocalDateTime current = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formatted = current.format(formatter);
//加上三位随机数
Random random = new Random();
int end3 = random.nextInt(999);
File file1 = new File(dir1.getAbsolutePath() + File.separator + formatted + "-" + end3 + "-" + multipartFile.getOriginalFilename());
try {
multipartFile.transferTo(file1);
} catch (IOException e) {
e.printStackTrace();
}
log.info("【上传薪资Excel文件已保存到本地:{}】",file1.getAbsolutePath());
//创建返回对象SalaryExcelOperatVo的实例化对象: result
SalaryExcelOperatVo result = new SalaryExcelOperatVo();
//获取excel文件sheet1 的内容
ArrayList<InSalary> inSalaries1 = readExcel1(file1.getAbsolutePath());
ArrayList<SalaryStaffPerOneListVo> vo1 = new ArrayList<>();
SalaryStaffPerOneListVo oneListVo ;
for(InSalary inSalary1:inSalaries1){
oneListVo = new SalaryStaffPerOneListVo();
BeanUtils.copyProperties(inSalary1,oneListVo);
vo1.add(oneListVo);
} result.setSheetOne(vo1);
//获取excel文件sheet2 的内容
ArrayList<InSalary> inSalaries2 = readExcel2(file1.getAbsolutePath());
ArrayList<SalaryStaffPerTwoListVo> vo2 = new ArrayList<>();
SalaryStaffPerTwoListVo twoListVo ;
for(InSalary inSalary2:inSalaries2){
twoListVo = new SalaryStaffPerTwoListVo();
BeanUtils.copyProperties(inSalary2,twoListVo);
vo2.add(twoListVo);
}
result.setSheetTwo(vo2);
return result;
}
八、StringUtils 工具类
StringUtils 提供了许多字符串操作相关的方法,其中比较常用的包括 isEmpty、isNotEmpty、isBlank 和 isNotBlank,它们的作用如下:
isEmpty:判断字符串是否为 null 或长度是否为 0;
isNotEmpty:判断字符串是否不为 null 且长度不为 0;
isBlank:判断字符串是否为 null、空字符串或全为空格字符;
isNotBlank:判断字符串是否不为 null、不为空字符串且不全为空格字符。
isEmpty 和 isNotEmpty 是根据字符串的长度来判断的,而 isBlank 和 isNotBlank 还包括对空格字符的处理。另外,如果字符串为 null,无论使用哪个方法都会返回 true。
以下是 StringUtils.isEmpty、StringUtils.isNotEmpty、StringUtils.isBlank 和 StringUtils.isNotBlank 的源码实现(相对简化),可以更好地理解它们的区别: public class StringUtils { // 判断字符串是否为空(长度是否为 0) public static boolean isEmpty(CharSequence cs) { return cs == null || cs.length() == 0; }
// 判断字符串是否不为空(长度是否不为 0)
public static boolean isNotEmpty(CharSequence cs) {
return !isEmpty(cs);
}
// 判断字符串是否为空白(包括 null、空字符串和全为空格字符)
public static boolean isBlank(CharSequence cs) {
int length;
if (cs == null || (length = cs.length()) == 0) {
return true;
}
for (int i = 0; i < length; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
// 判断字符串是否不为空白(不包括 null、空字符串和全为空格字符)
public static boolean isNotBlank(CharSequence cs) {
return !isBlank(cs);
}
} private void validChoiceInfo(InSalary data, AnalysisContext context) {
if(isBlank(data.getUserName())){
throw new ExcelAnalysisException(String.format("上传失败:第%s行员工姓名为空",context.readRowHolder().getRowIndex()));
}
if(isBlank(data.getIdNumber())){
throw new ExcelAnalysisException(String.format("上传失败:第%s行员工身份证信息为空",context.readRowHolder().getRowIndex()));
}
}
private void validChoiceInfo(InSalary data, AnalysisContext context) {
if(StrUtil.isBlank(data.getUserName())){ throw new ExcelAnalysisException(String.format("上传失败:第%s行员工姓名为空",context.readRowHolder().getRowIndex())); } if(StrUtil.isBlank(data.getIdNumber())){ throw new ExcelAnalysisException(String.format("上传失败:第%s行员工身份证信息为空",context.readRowHolder().getRowIndex())); } } 这两段代码实现的功能是一样的,都是对员工姓名和身份证信息进行非空判断,并在为空的情况下抛出异常。不同之处在于第一段代码使用了 isBlank() 方法,而第二段则使用了 StrUtil.isBlank() 方法。isBlank() 方法是在 JDK 11 中新增的,能够判断字符串是否为空或全为空格,因此不需要再使用 trim() 方法进行判断。而 StrUtil.isBlank() 方法是在 Hutool 库中提供的,功能与 isBlank() 类似,但需要将 Hutool 库引入项目中才能使用。另外,两段代码还有一个细微的差别。第一段代码使用了 if(isBlank(data.getUserName())) 的形式进行判断,而第二段代码使用了 if(StrUtil.isBlank(data.getUserName())) 的形式。这两种写法的效果是一样的,只是调用方法的方式略有不同。
或者自定义一个判空工具类(根据具体具体业务逻辑可以进行修改) import java.util.Collection; import java.util.Map;
public class NotNullCheck {
public NotNullCheck() {
}
public static boolean str(String str) {
return str != null && !str.isEmpty() && str.length() >= 1 && !"".equals(str.replaceAll(" ", ""));
}
public static boolean array(Collection list) {
return list != null && !list.isEmpty() && list.size() != 0;
}
public static boolean map(Map map) {
return map != null && !map.isEmpty() && map.size() != 0;
}
}
### 一、数据判空
开发中判空时推荐使用工具
库:StringUtils、CollectionUtils、ArrayUtils、Objects、NumberUtils
1、字符串判空
// 方式1:判断是否为 null 或空字符串
if (str == null || str.isEmpty()) {}
// 方式2:判断是否为 null 或空字符串
if (str == null || str.length() == 0) {}
// 方式3:判断是否为 null 或空字符串
if (str == null || str.equals("")) {}
// 方式3:判断是否为 null 或空字符串或空格或空白符
if (str == null || str.trim().length() == 0) {}
推荐使用:
// 方式1:判断是否为 null 或空字符串
if (StringUtils.isEmpty(str)) {}
// 方式2:判断是否不为 null 或空字符串
if (StringUtils.isNotEmpty(str)) {}
// 方式3:判断是否有任意一个为 null 或空字符串
if (StringUtils.isAnyEmpty(str, str2, str3)) {}
// 方式4:判断是否全部都不为 null 或空字符串,跟 isAnyEmpty 相反,可以用来做表单必填参数校验
if (StringUtils.isNoneEmpty(str, str2, str3)) {}
// 方式5:判断是否为 null 或空字符串或空格或空白符
if (StringUtils.isBlank(str)) {}
// 方式6:判断是否不为 null 或空字符串或空格或空白符
if (StringUtils.isNotBlank(str)) {}
// 方式7:判断是否有任意一个为 null 或空字符串或空格或空白符
if (StringUtils.isAnyBlank(str, str2, str3)) {}
// 方式8:判断是否全部都不为 null 或空字符串或空格或空白符,跟 isAnyBlank 相反,可以用来做表单必填参数校验
if (StringUtils.isNoneBlank(str, str2, str3)) {}
2、Integer 判空
基本数据类型 int 是不能为 null 的,只有包装类型 Integer 才能赋值为 null
if (integer == null || integer.equals(0)) {}
if (integer == null || integer.intValue() == 0) {}
推荐使用:
if (NumberUtils.isNullorZero(number)) {}
3、对象判空
if (obj == null) {}
if (obj != null) {}
推荐使用:
if (Objects.isNull(obj)) {}
if (Objects.nonNull(obj)) {}
4、List判空
// 方式一
if (list == null || list.size() == 0) {}
// 方式二
if (list == null || list.isEmpty) {}
推荐使用:
if (CollectionUtils.isEmpty(list)) {}
5、Map判空
// 方式一
if (map == null || map.size() == 0) {}
// 方式二
if (map == null || map.isEmpty) {}
推荐使用:
if (CollectionUtils.isEmpty(map)) {}
6、Set判空
// 方式一
if (set == null || set.size() == 0) {}
// 方式二
if (set == null || set.isEmpty) {}
推荐使用:
if (CollectionUtils.isEmpty(set)) {}
7、数组判空
注意 java 中数组长度是不可变的,而且只能储存同一种类型的数据,length 是数组的一个长度属性并不是方法,表示当前数组可以储存多少个长度的数据。字符串 String 的 length() 是一个方法,返回的是字符串的长度。集合类型的长度是要用 size 方法去获取,集合没有 length 属性。
if (array == null || array.length == 0) {}
推荐使用:
if (ArrayUtils.isEmpty(array)) {}
常见疑问:
- CollectionUtils.isEmpty 和 Objects.isNull 的区别
前者判断集合是否为 null 或空集合,后者仅判断是否为 null
- CollectionUtils.isEmpty 和 == null 的区别
前者判断集合是否为 null 或空集合,后者仅判断是否为 null
- Objects.isNull 和 == null 的区别
两者是等价的,可以看 Objects.isNull 的源码
public static boolean isNull(Object obj) {
return obj == null;
}
二、数据比较
基本数据类型比较
double、float、long、int、short、byte、char、boolean 这8种基本数据类型比较,可以直接用 == 进行比较
int age = 10;
double myAge = 18.5;
if (myAge > age) {} // true
引用数据类型比较
引用数据类型直接用 == 或用 equals 方法比较的都是引用地址是否相等,不过注意字符串 String 因为重写了 equals 方法是个例外。
字符串比较
java 中字符串是引用数据类型,并不能直接像 javascript 中那样直接用 == 判断是否相等,而是需要使用 equals 方法去判断是否相等:
String xiaoMing = "xiaoming";
String xiaoHong = "xiaoming";
if (xiaoMing == xiaoHong) {} // true
if (xiaoMing.equals(xiaoHong)) {} // true
上面的比较很多人会纳闷为什么用 == 直接比较两个字符串也是 true,那是不是说字符串我们也是可以直接用 == 去比较的,这个就涉及字符串常量池
了,实际我们换一种赋值方式你就能发现又不等了:
String xiaoMing = "xiaoming";
String xiaoHong = new String("xiaoming");
if (xiaoMing == xiaoHong) {} // false
if (xiaoMing.equals(xiaoHong)) {} // true
只用记住字符串你就用 equals 去比较值是否相等就行了。
三、类型转换
自动类型转换(显示转换)
范围小的转成大的,不会有任何问题,java 会自动为我们进行转换:double -> float -> long -> int -> short -> byte,注意byte、short和char不能相互转换
int age = 18;
double money = age;
强制类型转换
(隐式转换
)
范围大的转成小的,直接赋值是会报错的,但是我们可以用小括号语法强制类型转换,但是这可能会导致数据精度损失或溢出:
double pi = 3.1415926;
int num = (int) pi; // 3,小数部分会丢失
数值类型转字符串
一般用于给前端返回的数据时常用到,可以用 String.valueOf 方法,或者直接用 + 加上空字符串:
int age = 18;
String userAge = String.valueOf(age);
String userAge2 = age + "";
字符串转数值
一般需要用于数学运算时,比如我们从其他地方获取到的数据是数字字符串,我们需要对起进行加减乘除必须要先转成数值类型,这时可以利用数值类型提供的方法来进行转换 Double.parseDouble、Float.parseFloat、Long.parseLong、Integer.parseInt()、Short.parseShort、Byte.parseByte,不过实际开发中要注意自己判空和捕获转换异常,推荐使用第三方库来转换:
String userAge = "18";
int age = Integer.parseInteger(age) * 3;