news 2026/7/6 14:13:08

【Java踩坑笔记】26_toString里打印对象?小心无限递归栈溢出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java踩坑笔记】26_toString里打印对象?小心无限递归栈溢出

26 | toString 里打印对象?小心无限递归栈溢出

摘要toString()里直接打印成员变量(对象类型),会触发该对象的toString(),如果互相引用就会无限递归,最终StackOverflowError。Lombok 的@ToString也会踩这个坑。


一、问题现象

publicclassToStringTest{classDepartment{privateStringname;privateCompanycompany;// 反向引用公司@OverridepublicStringtoString(){return"Department{name='"+name+"', company="+company+"}";// ↑// 触发 company.toString()}}classCompany{privateStringname;privateList<Department>depts;@OverridepublicStringtoString(){return"Company{name='"+name+"', depts="+depts+"}";// ↑// 触发 department.toString()}}publicstaticvoidmain(String[]args){Companyc=newCompany("ACME");Departmentd=newDepartment("IT",c);c.setDepts(List.of(d));System.out.println(c);// ❌ StackOverflowError}}

运行结果:

Exception in thread "main" java.lang.StackOverflowError at java.base/java.lang.String.valueOf(String.java:3244) at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) at Company.toString(ToStringTest.java:xx) at Department.toString(ToStringTest.java:xx) at Company.toString(ToStringTest.java:xx) ...

二、踩坑现场

场景 1:Lombok @ToString 默认包含所有字段

// ❌ 危险:@ToString 默认打印所有字段,包括对象类型@ToStringclassOrder{privateLongid;privateUseruser;// User 对象,也会调用 user.toString()privateList<OrderItem>items;}@ToStringclassUser{privateLongid;privateList<Order>orders;// 反向引用,无限递归!}

现象:打印order.toString()时,触发user.toString(),再触发orders.toString(),再触发order.toString()… 无限递归。

场景 2:日志打印对象

// ❌ 错误:日志直接打印对象,触发 toString()log.info("处理订单:{}",order);// 触发 order.toString()// 如果 order 的 toString() 里有循环引用 → StackOverflowError// 更严重的是:这个错误可能在生产环境才触发(数据特定时)

三、原理解析

3.1 无限递归的形成

System.out.println(company) → company.toString() → 拼接 depts(List) → 遍历 List,对每个元素调用 toString() → d1.toString() → 拼接 company → company.toString() ← 又回来了! → 拼接 depts...

递归深度每两轮 +2,直到超过 JVM 栈深度限制(通常 1024~15000,取决于-Xss设置),抛出StackOverflowError

3.2 Lombok @ToString 的坑

@ToString// 默认:includeFieldNames = true,打印所有字段classOrder{privateUseruser;// 会调用 user.toString()}

@ToString有一个配置:callSuper = false(默认),且不排除任何字段。如果字段是对象类型,就会触发该对象的toString()

3.3 哪些场景会触发 toString()?

触发方式说明
System.out.println(obj)自动调用obj.toString()
字符串拼接+" obj自动调用obj.toString()
日志框架log.info("{}", obj)大部分日志框架会调用obj.toString()
String.valueOf(obj)内部调用obj.toString()

四、正确写法

4.1 @ToString 排除导致循环的字段

// ✅ 正确:排除导致循环的字段@ToString(exclude="user")// Lombok 旧版本写法classOrder{privateLongid;privateUseruser;// 不打印 user}// ✅ Lombok 新版本:用 @ToString.ExcludeclassOrder{privateLongid;@ToString.Exclude// 排除单个字段privateUseruser;}

4.2 手动重写 toString(),避免打印整个对象

// ✅ 正确:toString() 只打印基本类型字段@OverridepublicStringtoString(){return"Order{id="+id+", userId="+userId+"}";// 只打印 userId,不打印 user 对象}

4.3 用 IDEA 生成 toString() 时,排除对象字段

IDEA →右键 → Generate → toString()→ 在弹窗里取消勾选对象类型的字段。

4.4 Jackson 序列化同理:用 @JsonIgnore

// ✅ Jackson 序列化时也会踩同样的坑classOrder{privateLongid;@JsonIgnore// 序列化时排除 user(防止无限递归)privateUseruser;}

五、最佳实践

✅ toString() 的 4 条铁律

  1. toString()里只打印基本类型字段不要打印对象类型字段
  2. 用 Lombok@ToString,记得排除循环引用字段
  3. 日志打印时,必要时手动拼接关键字段,不要直接打印整个对象
  4. Jackson 序列化用@JsonIgnore@JsonManagedReference/@JsonBackReference处理双向引用

🔍 如何快速发现循环引用?

# 用 jcmd 或 jmap 导出堆快照jmap -dump:live,format=b,file=heap.hrof<pid># 用 Eclipse MAT 分析# 搜索:java.lang.StackOverflowError# 查看堆栈,找到递归调用的 toString()

🛠️ IDEA 的 toString() 生成技巧

右键 → Generate → toString()→ 在对话框里:

  • 勾选Use field name(字段名 = 值,清晰)
  • 取消勾选对象类型字段(防止递归)
  • 点击Setting→ 勾选Skip null values(忽略 null 字段,输出更简洁)

六、小结

  • toString()里打印对象类型字段,会触发该对象的toString(),双向引用时无限递归
  • Lombok@ToString默认包含所有字段,要注意循环引用
  • toString()里只打印基本类型,对象类型用exclude排除
  • 日志打印对象前,确认toString()不会递归
  • Jackson 序列化也有同样问题,用@JsonIgnore处理

下一篇预告:大量使用反射,性能到底差多少?—— 反射调用比直接调用的性能差距,以及Method缓存的最佳实践。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/6 14:12:54

校园体育器材管理系统

系统概述基于若依框架的校园体育器材管理模块&#xff0c;采用器材档案 借用记录设计&#xff0c;管理员维护器材信息与库存&#xff0c;学生在线查看并发起借用、续租、归还。目标维护体育器材信息与库存数据提供器材查询与借用功能管理借用、续租、归还流程与状态记录按角色…

作者头像 李华
网站建设 2026/7/6 14:12:57

Aura:俄罗斯大神打造的免费白噪音神器,把森林搬进你的Windows桌面!

在这个信息过载、注意力稀缺的年代&#xff0c;想要专注工作、放松入睡&#xff0c;却发现周围环境嘈杂得让人抓狂&#xff1f;耳机里循环的歌单听腻了&#xff0c;付费白噪音APP又贵又占内存……今天必须给你安利一款来自俄罗斯的宝藏工具——Aura&#xff0c;由开发者 Alexan…

作者头像 李华
网站建设 2026/7/6 14:12:35

AI项目交付完就结束了?不,真正值钱的是后面的事

AI项目交付完就结束了&#xff1f;不&#xff0c;真正值钱的是后面的事 AI系统跟传统软件正好相反&#xff1a;传统软件交付那天是巅峰&#xff0c;之后慢慢过时&#xff1b;AI系统交付那天只是起点&#xff0c;持续喂数据、修正错误案例&#xff0c;准确率会从上线时的90%左右…

作者头像 李华
网站建设 2026/7/6 14:09:05

Claude Code Auto memory 的审计与编辑机制,为什么普通 markdown 才是最可靠的工程记忆

今天聊 Claude Code 的 Auto memory,不从宏大的智能体叙事切入,而从一个很具体的动作切入,打开 /memory。这个动作看起来小,背后却牵住了 Claude Code 记忆机制里最关键的一条线,Claude 记录下来的东西,不是藏在某个黑盒数据库里,也不是只能通过产品界面间接管理,而是落…

作者头像 李华
网站建设 2026/7/6 14:06:31

手写 MCP Server 控制文件系统:50 行代码让 AI 学会读写文件

上周发了篇手写 MCP Server 连数据库的文章&#xff0c;有个读者留言说&#xff1a;“能不能也写篇怎么让 AI 读写文件的&#xff1f;”我当时愣了一下——好像确实&#xff0c;网上聊 MCP 的大多都在讲数据库、API、浏览器自动化&#xff0c;很少有人提最基础的“读写文件”这…

作者头像 李华