为何ToStringBuilder表现不一致?两次System.out.println(person)输出差异原因
1. 两次System.out.println(person)输出不同的原因
首先得明确:System.out.println(obj)本质是调用obj.toString(),所以两次输出不同意味着两次调用person.toString()的结果完全不一样。你觉得中间的person.put("a", "b")无关,其实它间接改变了person的内部状态,进而影响了toString()的执行逻辑。
结合你描述的现象——第一次输出Job@28f67ac7(这是Object类默认的toString()格式:类名@哈希码),第二次输出Manager(Job重写后的toString()结果),核心原因大概率是:
- 第一次调用
person.toString()时,ToStringBuilder没有触发Job对象的toString()调用。可能是person内部的Job属性在此时未被ToStringBuilder的逻辑识别到——比如是延迟加载的属性、反射访问权限受限,或是ToStringBuilder的配置只遍历已存在的Map entry; - 执行
person.put("a", "b")后,person的内部结构(比如Map的entry集合、Bean的属性缓存)发生了变化,第二次调用person.toString()时,ToStringBuilder的逻辑能正确遍历到Job对应的属性/entry,自然就调用了Job的toString()方法,得到了Manager的结果。
举个常见场景:如果Person是基于Map实现的Bean容器(比如Apache Commons的BeanMap),第一次调用toString()时,Job对应的属性还未被映射到Map的entry中,ToStringBuilder只输出了已有的entry;而put("a", "b")触发了BeanMap的全量属性扫描,第二次调用时Job属性被纳入遍历范围,就出现了不同的输出。
2. ToStringBuilder工作表现不一致的常见原因
结合你的问题,ToStringBuilder出现不稳定表现通常和以下几点有关:
- 依赖对象的动态状态:尤其是反射模式的
ReflectionToStringBuilder,输出完全依赖对象当前的字段/属性状态。如果对象内部结构(比如Map的entry、Bean的属性缓存)在两次调用间发生变化,输出结果自然不同; - 配置模式不一致:如果混用了手动字段追加(
append()方法)和反射模式,或者反射模式的配置(比如是否忽略transient字段、是否包含父类属性)在运行时被修改,会导致输出逻辑混乱; - 延迟加载/初始化的影响:如果被处理的对象包含延迟加载的属性(比如你的
Job属性),第一次调用时属性尚未初始化,ToStringBuilder只能输出对象的默认哈希码;后续操作触发属性初始化后,第二次调用就能输出正确的toString()结果; - 缓存机制的干扰:某些自定义的ToStringBuilder实现会缓存第一次生成的字符串,如果对象状态变化后缓存未失效,会导致输出和实际状态不符;但你的情况是第一次未调用
Job的toString(),所以更可能是缓存未包含后续新增的属性。
快速验证建议
你可以在Job类的toString()方法中加个日志,确认两次调用person.toString()时是否触发了Job.toString():
public class Job { @Override public String toString() { System.out.println("Job.toString()被调用了"); return "Manager"; } }
同时检查Person类的toString()实现,确认ToStringBuilder是如何遍历属性/entry的——如果是基于Map的实现,对比两次调用时entrySet()的内容是否一致。
内容的提问来源于stack exchange,提问作者Brian Schack

