成员列表变量空值校验:@NotNull列表与普通列表差异化处理
最优实现方案:基于反射+注解的通用列表校验/清理工具
针对你给出的类结构和校验需求,我推荐封装一个通用反射工具类来处理——这种方案既能覆盖所有嵌套层级的字段,又不用为每个类重复写逻辑,是最灵活高效的实现方式。
核心逻辑梳理
- 用反射遍历目标对象的所有字段,精准定位
List类型的字段 - 针对列表字段分两种情况处理:
- 带
@NotNull注解的列表:严格检查元素,只要有null就抛出校验异常 - 不带注解的列表:自动过滤掉所有
null元素,保留有效数据
- 带
- 递归处理嵌套对象(比如A里的C、B,B里的E),确保所有层级的列表都被处理到
代码实现示例
首先定义一个自定义校验异常,用于抛出列表包含null的错误:
public class NotNullListContainsNullException extends RuntimeException { public NotNullListContainsNullException(String message) { super(message); } }
然后是核心的工具类,实现反射解析和列表处理逻辑:
import javax.validation.constraints.NotNull; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class ListFieldProcessor { // 对外暴露的入口方法,传入需要处理的对象即可 public static void process(Object target) throws IllegalAccessException { if (target == null) { return; } Class<?> clazz = target.getClass(); // 遍历当前类的所有字段(如果需要处理父类字段,可以扩展逻辑) for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); Object fieldValue = field.get(target); // 处理List类型的字段 if (List.class.isAssignableFrom(field.getType())) { List<?> list = (List<?>) fieldValue; if (list == null) { continue; // 列表本身为null的场景,需求未明确要求,暂时跳过 } // 检查字段是否标注了@NotNull注解 if (field.isAnnotationPresent(NotNull.class)) { // 校验列表中是否存在null元素 if (list.contains(null)) { throw new NotNullListContainsNullException( String.format("字段[%s]标注了@NotNull,但列表中包含null元素", field.getName()) ); } } else { // 过滤掉列表中的null元素,替换原列表 List<Object> filteredList = new ArrayList<>(); for (Object item : list) { if (item != null) { filteredList.add(item); } } field.set(target, filteredList); } } // 递归处理嵌套的自定义对象(排除基本类型、包装类和字符串) else if (!field.getType().isPrimitive() && !isWrapperType(field.getType()) && !String.class.equals(field.getType())) { process(fieldValue); } } } // 辅助方法:判断是否是基本类型的包装类 private static boolean isWrapperType(Class<?> clazz) { return clazz.equals(Integer.class) || clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Boolean.class) || clazz.equals(Character.class) || clazz.equals(Byte.class) || clazz.equals(Short.class); } }
使用示例
public class Main { public static void main(String[] args) throws IllegalAccessException { // 构造测试对象 A a = new A(); a.ds = List.of("a", null, "b"); // 标注@NotNull的列表,包含null元素 a.bs = List.of(new B()); a.c = new C(); a.c.s = List.of("x", null, "y"); // 未标注@NotNull的列表 try { ListFieldProcessor.process(a); } catch (NotNullListContainsNullException e) { System.err.println(e.getMessage()); // 会输出:字段[ds]标注了@NotNull,但列表中包含null元素 } // 处理后,a.c.s会被清理为["x", "y"] System.out.println(a.c.s); } }
方案优势
- 通用性:不需要为每个实体类单独编写处理逻辑,传入任意对象即可完成全量字段处理
- 深层覆盖:自动递归处理嵌套对象中的列表字段,不会遗漏层级
- 可扩展性:可以轻松扩展支持Set等其他集合类型,或者添加更多注解规则
- 性能可控:反射开销在大多数业务场景下可忽略,若有性能需求可以缓存字段元信息
注意事项
- 如果你的
@NotNull不是javax.validation.constraints.NotNull,需要修改工具类中的注解引用 - 对于不可变列表(比如
List.of()创建的),工具类会自动创建新列表赋值,避免修改异常 - 若需要处理父类的字段,可以扩展逻辑遍历所有父类的字段
内容的提问来源于stack exchange,提问作者Aditya Narayan Dixit
相关产品推荐
相关产品推荐

