一、背景

最近在做对接美国的EasyPost快递平台时,发现使用Objects.isNull()判断EasyPost返回的序列化之后的空实体(JSON体为:{})时,返回结果并不是false;然后情不自禁就自己写了个使用反射判断Java对象是否为“NULL”的工具类。

不过最后处于效率的考虑,我并没用,而是使用业务上的唯一约束做进一步的判空处理。这个工具类就送给有缘人吧,哈哈哈!

Java反射应用:判断对象是否为NULL_属性值

最终版工具类

只想知道最终工具类的老哥,请copy如下代码:

package com.saint.javabase.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Objects;

/**
* @author Saint
*/
public class CheckObjectIsNullUtils {

/**
* 不纳入判空逻辑的field属性
*/
static final HashSet<String> fieldHash = new HashSet<>(4);

static {
fieldHash.add("serialVersionUID");
}

/**
* 判断一个对象是否为null
*
* @param object
* @return
*/
public static boolean isNull(Object object) {
// 获取object的Class对象
Class<?> clazz = object.getClass();
// 获取对象的所有属性
Field[] fields = clazz.getDeclaredFields();
// 定义返回结果
boolean flag = true;

for (Field field : fields) {
// 使非Public类型的属性可以被访问
field.setAccessible(true);
Object fieldValue = null;
Type type = null;

try {
fieldValue = field.get(object);
// 获取到属性类型
type = field.getType();
// 获取属性名称
String fieldName = field.getName();
if (fieldHash.contains(fieldName)) {
continue;
}

// TODO 实际应用中建议删掉这一行,仅做测试使用
System.out.println("属性类型:" + type + ", 属性名:" + fieldName + ", 属性值:" + fieldValue);

} catch (Exception e) {
// TODO 真实业务场景中,这里可以采用打日志替换
e.printStackTrace();
}

// 只要有一个属性值不为null 就返回false 表示对象不为null
if (fieldValue != null) {

// 如果fieldValue不为null,并且fieldValue的值等于false时,则不认为对象不为空。
if (Objects.equals(type.getTypeName(), "boolean")
&& Objects.equals(fieldValue, false)) {
continue;
} else {
flag = false;
break;
}
}
}

return flag;
}
}

二、判空方式

判断Java对象是否为null可以有两层含义:

(1)第一层: 直接使用 object == null 去判断,对象为null的时候返回true,不为null的时候返回false。

(2)第二层:在object != null 为true的情况 下,​​进一步去判断对象的所有属性是否为null​​。

1)第一层判空

建议使用上图的Objects.isNull()方法,比自己写null ===xxx更加优雅。当然这里所做的是只是对象是否null,如果对象被new出来了,但是它的所有属性都是默认值null,Objects.isNull()方法是判断不出来的。

尤其是在前后端交互的过程中,前端传过来的空对象实际是:{},这时对象不为null,但是对象的所有属性为null。

2)使用反射做第二层判空

User.class

package com.saint.javabase.reflect;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
* @author Saint
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

private String name;
private Boolean age;
private List<String> hobbies;
private boolean student;

}

工具类:CheckObjectIsNullUtils

package com.saint.javabase.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Type;

/**
* @author Saint
*/
public class CheckObjectIsNullUtils {

/**
* 判断一个对象是否为null
*
* @param object
* @return
*/
public static boolean isNull(Object object) {
// 获取object的Class对象
Class<?> clazz = object.getClass();
// 获取对象的所有属性
Field[] fields = clazz.getDeclaredFields();
// 定义返回结果
boolean flag = true;

for (Field field : fields) {
// 使非Public类型的属性可以被访问
field.setAccessible(true);
Object fieldValue = null;

try {
fieldValue = field.get(object);
// 获取到属性类型
Type type = field.getType();
// 获取属性名称
String fieldName = field.getName();

// TODO 实际应用中建议删掉这一行,仅做测试使用
System.out.println("属性类型:" + type + ", 属性名:" + fieldName + ", 属性值:" + fieldValue);

} catch (Exception e) {
// TODO 真实业务场景中,这里可以采用打日志替换
e.printStackTrace();
}

// 只要有一个属性值不为null 就返回false 表示对象不为null
if (fieldValue != null) {
flag = false;
break;
}
}

return flag;
}
}

1>>>>>当含有一个boolean类型的属性时

测试类:

package com.saint.javabase.reflect;

/**
* 对象是否为空工具-测试类
* @author Saint
*/
public class CheckObjectIsNullTest {
public static void main(String[] args) {
User user = new User();
boolean flag = CheckObjectIsNullUtils.isNull(user);
System.err.println("User对象是否为空: " + flag);
}
}

控制台输出:

属性类型:class java.lang.String, 属性名:name, 属性值:null
属性类型:class java.lang.Boolean, 属性名:age, 属性值:null
属性类型:interface java.util.List, 属性名:hobbies, 属性值:null
属性类型:boolean, 属性名:student, 属性值:false
User对象是否为空: false

从输出上来看,收到boolean类型的影响,结果为false。

另外需要注意的是数据类型: boolean与Boolean
boolean 定义的变量默认值为false,Boolean定义的变量默认值为 null.

ps: 在定义boolean类型变量时,最好不要使用 isXxx,因为默认生成的get方法就是 isXxx(), RPC框架在反向解析的时候,以为对应的属性名是 xxx,从而导致属性获取不到,抛出异常。

2>>>>>修改工具类,兼容boolean类型的属性

工具类:CheckObjectIsNullUtils

package com.saint.javabase.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Objects;

/**
* @author
*/
public class CheckObjectIsNullUtils {

/**
* 判断一个对象是否为null
*
* @param object
* @return
*/
public static boolean isNull(Object object) {
// 获取object的Class对象
Class<?> clazz = object.getClass();
// 获取对象的所有属性
Field[] fields = clazz.getDeclaredFields();
// 定义返回结果
boolean flag = true;

for (Field field : fields) {
// 使非Public类型的属性可以被访问
field.setAccessible(true);
Object fieldValue = null;
Type type = null;

try {
fieldValue = field.get(object);
// 获取到属性类型
type = field.getType();
// 获取属性名称
String fieldName = field.getName();

// TODO 实际应用中建议删掉这一行,仅做测试使用
System.out.println("属性类型:" + type + ", 属性名:" + fieldName + ", 属性值:" + fieldValue);

} catch (Exception e) {
// TODO 真实业务场景中,这里可以采用打日志替换
e.printStackTrace();
}

// 只要有一个属性值不为null 就返回false 表示对象不为null
if (fieldValue != null) {

// 如果fieldValue不为null,并且fieldValue的值等于false时,则不认为对象不为空。
if (Objects.equals(type.getTypeName(), "boolean")
&& Objects.equals(fieldValue, false)) {
continue;
} else {
flag = false;
break;
}
}
}

return flag;
}
}

此时运行测试类结果如下:

Java反射应用:判断对象是否为NULL_工具类_02

从结果上来看,已经符合我们的预期了。

3>>>>>当User类实现序列化接口 Serializable 时

User类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

private static final long serialVersionUID = 1L;

private String name;
private Boolean age;
private List<String> hobbies;
private boolean student;

}

运行测试类输出如下:

Java反射应用:判断对象是否为NULL_反射_03

结果为false,因为serialVersionUID有值。

所以我们在判空时需要排除一些特殊的字段。

4>>>>>修改工具类,兼容特殊字段serialVersionUID

工具类CheckObjectIsNullUtils:

public class CheckObjectIsNullUtils {

/**
* 不纳入判空逻辑的field属性
*/
static final HashSet<String> fieldHash = new HashSet<>(4);

static {
fieldHash.add("serialVersionUID");
}

/**
* 判断一个对象是否为null
*
* @param object
* @return
*/
public static boolean isNull(Object object) {
// 获取object的Class对象
Class<?> clazz = object.getClass();
// 获取对象的所有属性
Field[] fields = clazz.getDeclaredFields();
// 定义返回结果
boolean flag = true;

for (Field field : fields) {
// 使非Public类型的属性可以被访问
field.setAccessible(true);
Object fieldValue = null;
Type type = null;

try {
fieldValue = field.get(object);
// 获取到属性类型
type = field.getType();
// 获取属性名称
String fieldName = field.getName();
if (fieldHash.contains(fieldName)) {
continue;
}

// TODO 实际应用中建议删掉这一行,仅做测试使用
System.out.println("属性类型:" + type + ", 属性名:" + fieldName + ", 属性值:" + fieldValue);

} catch (Exception e) {
// TODO 真实业务场景中,这里可以采用打日志替换
e.printStackTrace();
}

// 只要有一个属性值不为null 就返回false 表示对象不为null
if (fieldValue != null) {

// 如果fieldValue不为null,并且fieldValue的值等于false时,则不认为对象不为空。
if (Objects.equals(type.getTypeName(), "boolean")
&& Objects.equals(fieldValue, false)) {
continue;
} else {
flag = false;
break;
}
}
}

return flag;
}
}

再运行测试类结果如下:

Java反射应用:判断对象是否为NULL_属性值_04


从结果来看,做判空是排除了serialVersionUID字段。

总结

综上所述,当你需要判断Java对象是否为null的时候,你可以先通过 obj == null 去判断,如果obj 不等于null,再根据业务需求决定是否需要进一步判断 obj的所有属性是否都为null。