当前位置: 首页 > news >正文

【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缓存的最佳实践。

http://www.cnnetsun.cn/news/3185415.html

相关文章:

  • 校园体育器材管理系统
  • Aura:俄罗斯大神打造的免费白噪音神器,把森林搬进你的Windows桌面!
  • AI项目交付完就结束了?不,真正值钱的是后面的事
  • Claude Code Auto memory 的审计与编辑机制,为什么普通 markdown 才是最可靠的工程记忆
  • 手写 MCP Server 控制文件系统:50 行代码让 AI 学会读写文件
  • 【Java转AI实战】第3讲:流式输出——LLM的“分页查询“,附生产级SSE踩坑实录
  • 告别数据分析小白!okbiye AI写作,一键搞定论文数据实证分析
  • 踩坑十多款AIGC检测工具后,我给实验室搭了套全本地的论文内容自查系统
  • Dify_搭建智能销售 Agent 工作流
  • 第38篇:数据组织(一):数据所有者的角色与责任
  • 第40篇:IT部门的重组:从“系统维护中心”到“能力集成中心”
  • G. Summmon (Codeforces 2241)
  • 华为MetaERP Oracle EBS COA(会计科目表)完整深度解析一、COA 核心设计哲学(财务架构师视角)COA 全称Chart of Accounts,在 EBS 底层由会计关键弹性域
  • 06-运放运用电路-PH测试
  • 别人卷SEO,我用AI吃搜索红利
  • VCG 拉普拉斯Mesh变形(三)
  • 如何永久保存你的网络小说?这款开源下载器支持200+网站
  • 测试零基础抓包前端调试学习路线、线上课程(不会F12/Charles/Fiddler专用)
  • 阶段二:AI手工测试2 Web/APP抓包:网络协议与抓包工具深度解析,测试执行的辅助技能
  • 【2026更新】Python基础学习指南(AI版)——09面向对象
  • 2026 最新 CTF 知识库:Web 漏洞、历年 WP、脚本工具与 AI 知识库
  • Terraform 状态文件深度解析:从原理到远程Backend的6大核心作用
  • CloudFormation Stack 概念全解析:从资源集合到自动化编排的核心引擎
  • 第48期 OpenAI的钱都花哪了?Capex支出全景调研
  • 用 100 行代码手搓一个 MCP Server,让 LLM 直接读你本地文件
  • 一个能优化skill的skill能不能优化它自己?——20 亿 token 烧完,我才看懂 AI 在作弊
  • 小米 NAS 火了:它抢的不是群晖,是你家的数据入口
  • 2026年最新八字排盘应用推荐:天乙八字排盘、命枢、问真八字等怎么选?
  • 《从 300 行到 30 万行代码,我学到的 5 个重构教训》
  • 大模型工具调用安全:从开放 API 到可控 Agent 的权限治理