一、文档说明
本文档记录了'输出对象时报 NullPointerException、输出属性不报错'问题的背景、根因、解决方法及验证步骤,适用于同类跨 JDK 版本兼容性问题的参考,可供开发人员排查故障、沉淀技术经验使用。
二、问题背景
2.1 现象描述
- 业务场景:输出自定义用户对象时,触发
java.lang.NullPointerException异常;单独输出对象的非日期类属性(如userId、、 等)时,无任何报错,属性值正常展示。
JDK 版本切换导致对象 toString() 方法抛出空指针异常,根因在于日期格式化逻辑未先校验入参是否为 null。高版本 JDK 的 DateUtils 底层可能内置空值校验返回 null,而低版本 JDK 直接调用 SimpleDateFormat 抛出异常。修复方案为调整校验顺序,先判断日期属性是否为 null 再调用格式化方法,并优化重复调用。同时建议在工具类中封装通用空值校验方法提升复用性。验证步骤包括单独测试工具类及跨版本环境复测。
本文档记录了'输出对象时报 NullPointerException、输出属性不报错'问题的背景、根因、解决方法及验证步骤,适用于同类跨 JDK 版本兼容性问题的参考,可供开发人员排查故障、沉淀技术经验使用。
java.lang.NullPointerException 异常;单独输出对象的非日期类属性(如 userId、、 等)时,无任何报错,属性值正常展示。emailnickNametoString() 方法未做任何代码改动,在高版本 JDK(如 JDK 11+/JDK 8u200+)环境中测试无异常,切换至低版本 JDK(如 JDK 8u100-/JDK 7)后,首次出现该空指针异常。@Override public String toString() {
return "用户 ID:" + (userId == null ? "空" : userId) + ",邮箱:" + (email == null ? "空" : email) + ",昵称:" + (nickName == null ? "空" : nickName) + ",0:直接加 1:同意后加好友:" + (joinType == null ? "空" : joinType) + ",性别 0 女 1 男:" + (sex == null ? "空" : sex) + ",密码:" + (password == null ? "空" : password) + ",个性签名:" + (personalSignature == null ? "空" : personalSignature) + ",状态:" + (status == null ? "空" : status) + ",创建时间:" + (DateUtils.format(createTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern()) == null ? "空" : DateUtils.format(createTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern())) + ",最后登录时间:" + (DateUtils.format(lastLoginTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern()) == null ? "空" : DateUtils.format(lastLoginTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern())) + ",地区名字:" + (areaName == null ? "空" : areaName) + ",地区编号:" + (areaCode == null ? "空" : areaCode) + ",最后离线时间:" + (lastOffTime == null ? "空" : lastOffTime);
}
对日期类型属性(createTime/lastLoginTime)的非空校验逻辑顺序倒置,是导致异常的根本原因:
DateUtils.format() 方法格式化日期,再判断格式化结果是否为 null,属于'先执行方法、后校验空值',本质是'踩坑后再判断是否有坑'。createTime 或 lastLoginTime 为 null 时,会直接触发 DateUtils.format(null, 格式) 调用,而该调用的行为依赖 JDK 版本,进而导致跨版本兼容性问题。DateUtils 底层依赖 JDK 原生日期类(如 SimpleDateFormat),不同版本 JDK 对 null 入参的处理逻辑不同,最终触发异常:
DateUtils.format(null, 格式) 底层会内置空值校验,直接返回 null,此时代码中的三元表达式可正常执行,返回'空'字符串,无异常抛出。DateUtils.format(null, 格式) 会直接调用 SimpleDateFormat.format(null),而低版本 SimpleDateFormat 无空值校验,直接抛出 NullPointerException,且未被捕获,导致代码未走到三元表达式就报错。单独输出对象非日期属性时无报错,是因为这些属性的空值校验逻辑正确(先判断属性是否为 null,再拼接值),不涉及工具类调用,与 JDK 版本差异无关。
createTime/lastLoginTime)是否为 null,再调用 DateUtils.format() 方法,从源头避免对 null 值执行格式化操作。DateUtils.format() 方法(原代码非空场景会调用 2 次),提前定义格式化结果变量,仅调用 1 次即可。@Override public String toString() {
// 先校验日期入参,再格式化,避免 null 触发异常,同时减少重复调用
String createTimeStr = (createTime == null) ? "空" : DateUtils.format(createTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern());
String lastLoginTimeStr = (lastLoginTime == null) ? "空" : DateUtils.format(lastLoginTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern());
// 拼接所有属性,使用预处理后的日期字符串
return "用户 ID:" + (userId == null ? "空" : userId) + ",邮箱:" + (email == null ? "空" : email) + ",昵称:" + (nickName == null ? "空" : nickName) + ",0:直接加 1:同意后加好友:" + (joinType == null ? "空" : joinType) + ",性别 0 女 1 男:" + (sex == null ? "空" : sex) + ",密码:" + (password == null ? "空" : password) + ",个性签名:" + (personalSignature == null ? "空" : personalSignature) + ",状态:" + (status == null ? "空" : status) + ",创建时间:" + createTimeStr + ",最后登录时间:" + lastLoginTimeStr + ",地区名字:" + (areaName == null ? "空" : areaName) + ",地区编号:" + (areaCode == null ? "空" : areaCode) + ",最后离线时间:" + (lastOffTime == null ? "空" : lastOffTime);
}
为避免重复编写日期空值校验逻辑,可在 DateUtils 工具类中封装通用方法,统一处理空值和格式化异常,提升代码复用性:
/**
* 带空值校验的日期格式化方法
* @param date 日期对象(可空)
* @param pattern 格式化模板
* @return 格式化后的字符串,空值返回'空',异常返回'格式错误'
*/
public static String formatWithNullCheck(Date date, String pattern) {
if (date == null) {
return "空";
}
try {
// 调用原有格式化方法
return format(date, pattern);
} catch (Exception e) {
// 兜底处理格式化异常(如模板错误、日期非法)
return "格式错误";
}
}
@Override public String toString() {
// 直接调用通用方法,简化代码
String createTimeStr = DateUtils.formatWithNullCheck(createTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern());
String lastLoginTimeStr = DateUtils.formatWithNullCheck(lastLoginTime, DateTimePatternEnum.YYYY_MM_DD_HH_MM_SS.getPattern());
// 其余属性拼接逻辑与修复后代码一致
return "用户 ID:" + (userId == null ? "空" : userId) + ",邮箱:" + (email == null ? "空" : email) + ",昵称:" + (nickName == null ? "空" : nickName) + ",0:直接加 1:同意后加好友:" + (joinType == null ? "空" : joinType) + ",性别 0 女 1 男:" + (sex == null ? "空" : sex) + ",密码:" + (password == null ? "空" : password) + ",个性签名:" + (personalSignature == null ? "空" : personalSignature) + ",状态:" + (status == null ? "空" : status) + ",创建时间:" + createTimeStr + ",最后登录时间:" + lastLoginTimeStr + ",地区名字:" + (areaName == null ? "空" : areaName) + ",地区编号:" + (areaCode == null ? "空" : areaCode) + ",最后离线时间:" + (lastOffTime == null ? "空" : lastOffTime);
}
DateUtils.format(null, 格式化模板),观察是否直接抛出 NullPointerException,验证 JDK 版本差异的影响。toString() 方法简化为 return "测试对象";,输出对象无异常,确认异常由日期格式化逻辑触发。createTime/lastLoginTime 设为 null,输出对象仍无异常,格式化结果正确返回'空'。本次异常本质是'日期格式化空值校验顺序错误',JDK 版本切换仅为触发条件——低版本 JDK 对 null 入参的容错性更低,暴露了原本隐藏的代码隐患,而非 JDK 版本直接导致异常。
null,再执行方法,避免依赖 JDK 版本的容错行为。SimpleDateFormat/DateUtils 对 null 入参的处理逻辑。toString(),异常触发与方法执行路径强相关。若修复后仍报错,可排查:1. DateUtils 依赖版本与低版本 JDK 不兼容,需降级依赖;2. 日期对象存在非法值(非 null 但无法格式化),需在通用方法中增加异常捕获兜底。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online