Java对象的“自我介绍术”:彻底搞懂toString()魔法

前言

当小Z在咖啡馆调试代码时,打印了Java对象,看着控制台输出的Student@2f4d3709陷入困惑。这个看似简单的字符串背后,隐藏着Java语言设计的深意——toString()是对象与世界对话的桥梁

在默认实现中,它返回的是类名+哈希码的"加密信息",但重写后却能成为调试利器、日志明星、甚至JSON序列化的优雅使者。

So,每个Java开发者都必须掌握toString()。🚀

So,准备好用一杯咖啡的时间,解锁Java对象的"自我介绍术"吧!☕️

一、现象分析

现象分析:打印对象输出了啥?🤔

小Z在调试代码时遇到了诡异现象,且看我慢慢道来:

小Z有一个Student类,有三个成员变量,一个有参构造方法,它的代码如下:

public class Student {private String name;private String sex;private Integer age;public Student(String name, String sex, Integer age) {this.name = name;this.sex = sex;this.age = age;}
}

新建测试类Test,创建student对象,并直接使用System.out.println()语句打印该对象。

public class Test {public static void main(String[] args) {Student student = new Student("小Z", "男", 16);System.out.println(student);}
}

执行结果如下:

com.example.Student@2f4d3709

“这是个啥?打印输出的是当前包下的类名,还有@字符,还有一串数字和字母。打印出类名很好理解,但是@、数字、字母是什么意思呀?”小Z抓狂地挠头。😖

"想象一下,你正端着拿铁看着代码,突然发现打印对象像在跟你说'猜猜我是谁?'——这就是Java留的彩蛋”。🎉🎉🎉

二、原理追踪

📜 原理追踪:toString()的前世今生

1. Object祖传代码揭秘

当我们在 Java 中使用System.out.println()或者其他输出语句,传入的参数是Java对象,程序将自动调用其所属类的toString()方法。“目前Student类中并没有toString()方法呀!是魔法🪄实现的吗?”小Z有大大的疑问。

并不是魔法。是所有 Java 类都直接或间接继承了Object类,而Object类中有一个默认的toString()方法,源码如下(基于JDK11):

/*** Returns a string representation of the object. In general, the* {@code toString} method returns a string that* "textually represents" this object. The result should* be a concise but informative representation that is easy for a* person to read.* It is recommended that all subclasses override this method.* <p>* The {@code toString} method for class {@code Object}* returns a string consisting of the name of the class of which the* object is an instance, the at-sign character `{@code @}', and* the unsigned hexadecimal representation of the hash code of the* object. In other words, this method returns a string equal to the* value of:* <blockquote>* <pre>* getClass().getName() + '@' + Integer.toHexString(hashCode())* </pre></blockquote>** @return  a string representation of the object.*/
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

该方法官方注释如下:

直译,见笑,见笑~😅
该方法返回对象的字符串表示形式。一般来说,toString方法返回一个“文本表示”此对象的字符串。结果应该是一个简洁但信息丰富的表示,便于人们阅读。建议所有子类重写此方法。
Object类的toString方法返回一个字符串,该字符串由该对象作为其实例的类的名称、@符号字符@以及该对象哈希码的无符号十六进制表示形式组成。换句话说,这个方法返回的字符串等于的值:getClass().getName() + @ + Integer.toHexString(hashCode())

这下明白了几分。我们的Student没有重写toString()方法,而是自动调用了Object类中的toString()方法。这时候打印输出的是一个字符串,其中包含对象的类名和哈希码。

以下是UML类图示意图,标注Object类到StudenttoString继承关系。

image

2.底层调用链揭秘

随着时间的推移,小Z发现了更深层次的秘密。咱们直接上代码:

// 底层调用链
System.out.println(obj); 
// 等价于 System.out.println(obj.toString()); 
// 实际调用链:String.valueOf(obj) -> obj.toString()

哦?为什么实际调用链式String.valueOf(obj) -> obj.toString()

执行System.out.println(obj)语句时,调用了java.io.PrintStream.println(obj)方法。方法中很明显调用了String.valueOf()方法。

/*** Prints an Object and then terminate the line.  This method calls* at first String.valueOf(x) to get the printed object's string value,* then behaves as* though it invokes {@link #print(String)} and then* {@link #println()}.** @param x  The {@code Object} to be printed.*/
public void println(Object x) {String s = String.valueOf(x);synchronized (this) {print(s);newLine();}
}

继续探索:String类中的valueOf()方法。

/*** Returns the string representation of the {@code Object} argument.** @param   obj   an {@code Object}.* @return  if the argument is {@code null}, then a string equal to*          {@code "null"}; otherwise, the value of*          {@code obj.toString()} is returned.* @see     java.lang.Object#toString()*/
public static String valueOf(Object obj) {return (obj == null) ? "null" : obj.toString();
}

通过源码可以看出,最终调用了Java对象的toString()方法。由于Student类中没有重写该方法,所以调用了超级父类ObjecttoString()方法。

小Z恍然大悟了:“真佩服自己,把源码看懂了耶!”。😁

三、答疑解惑

答疑解惑:打印了对象的地址,说法正确吗?❌

有时候我们总是说打印了该对象的地址,其实是不准确的。打印的内容一个是类名,一个是哈希码。打印的对象的哈希码并不是引用地址,尽管在某些情况下它们可能看起来像是一样的。

哈希码是一个整数,它是由 Java 运行时环境根据对象的内部状态计算出来的。对于一个类的任何两个不同的实例,它们的哈希码通常不会相同。也就是说,如果在打印一个对象时,看到了两个不同的哈希码,可以确定这两个对象是不同的,它们占用不同的内存空间。

而引用地址是内存中的一个地址值,它表示某个对象在内存中的位置。对于同一个对象,无论它被引用多少次,它的引用地址始终是相同的。两个不同的对象的引用地址也是不同的。

通常情况下,打印一个 Java 对象时显示的是该对象的字符串表示,这个字符串既包括了实例的哈希码,也包括了它的一些其他信息。因此,在某些情况下,打印出来的哈希码和引用地址可能看起来相同,但实际上它们是不同的概念。

😪小Z看了一大推的说明,努力理解着。(额,快睡着了~)

四、实战指南

🛠️ 实战指南:手把手打造优雅toString()

如何打印对象内部信息呢,比如打印出对象中姓名、性别和年龄。这时需要我们自定义或是重写Student类的toString()方法。

1.必备要素清单

✅ 类型标识(防止类型混淆)

✅ 关键字段展示(建议包含业务主键)

✅ 时间格式化(比如统一使用yyyy-MM-dd HH:mm:ss

❌ 敏感信息(密码字段请自觉打码)

❌ 循环引用(小心栈溢出!)

2.高效编程技巧

IDE自动生成:解放双手

@Override
public String toString() {return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE).append("name", getName()).append("sex", getSex()).append("age", getAge()).toString();
}

Lombok魔法:一行注解搞定复杂toString

@Data // Lombok注解:自动生成Getter、Setter、toString、equals、hashCode方法
@ToString(exclude = {"password", "age"}) // 自定义toString(),排除敏感字段
public class Student{private String name; // 普通字段,会自动生成Getter/Setterprivate String sex;private transient Integer age; // transient字段,不参与序列化和哈希计算
}

手写高性能版(并发场景推荐)

@Override
public synchronized String toString() {StringBuilder sb = new StringBuilder("Student{");sb.append("name=").append(name);sb.append(", sex='").append(sex).append('\'');sb.append(", createdTime=").append(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(createdAt));sb.append('}');return sb.toString();
}

如果我们再次执行Test中的main()方法,这时就调用了被重写的toString()方法,并按照我们的意愿对student对象进行了打印输出啦。

小Z不由感叹:“这可相当于Java对象的身份证号码,但开发者常忘记重写!”😮‍💨

五、避坑预警

⚠️ 避坑预警:这些雷区不要踩!

1. 反面教材警示

注意,以下是错误示范哦~

// 错误示范1:泄露敏感信息
@Override
public String toString() {return "Student{" + "password='" + password + "'}"; // 安全事故隐患!
}// 错误示范2:无限递归
class Manager extends Employee {// 未重写导致打印父类字段 → 无限递归栈溢出
}

2. 测试验证秘籍

// JUnit断言检查输出格式
assertThat(student.toString()).contains("sex=男").doesNotContain("password").startsWith("Student{name=");

六、彩蛋时刻

彩蛋时刻:在工作中让对象"说话"的黑科技

// Spring Boot优雅方案
@RestController
public class StudentController {@GetMapping("/student")public ResponseEntity<?> getStudent(@RequestBody Student student) {// 自动调用toString()log.info("Received request for student: {}", student); return ResponseEntity.ok(student);}
}// JSON美化输出
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(student));

总结

  1. toString()现象分析:打印Student对象输出的是类名和哈希码的组合,这是因为Student类没有重写Object类的toString()方法。

  2. toString()原理追踪

    • Object祖传代码揭秘:所有Java类都直接或间接继承了Object类,Object类的toString()方法返回类名、@符号和哈希码的无符号十六进制表示形式。

    • 底层调用链揭秘System.out.println(obj)语句实际调用了String.valueOf(obj)obj.toString(),最终调用Java对象的toString()方法。

  3. 答疑解惑:打印对象时输出的内容是类名和哈希码,哈希码是根据对象内部状态计算的整数,与引用地址不同。

  4. 实战指南

    • 必备要素清单:类型标识、关键字段展示、时间格式化、敏感信息处理、循环引用处理。

    • 高效编程技巧IDE自动生成、Lombok魔法、手写高性能版。

  5. 避坑预警

    • 反面教材警示:泄露敏感信息会导致安全事故隐患,无限递归会导致栈溢出。

    • 测试验证秘籍:使用JUnit断言检查输出格式。

  6. 彩蛋时刻:在工作中可通过Spring Boot让对象“说话”。

总之,掌握toString()方法对Java开发者至关重要,它可以帮助我们更好地调试代码、输出对象信息,提高开发效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/899053.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

读DAMA数据管理知识体系指南20数据安全风险

读DAMA数据管理知识体系指南20数据安全风险1. 数据安全制约因素 1.1. 保密等级1.1.1. 保密意味着机密或私密1.1.2. 机密信息仅在“需要知道”的基础上共享1.1.3. 保密等级取决于谁需要知道某些类型的信息1.2. 监管要求1.2.1. 根据外部规则(如法律、条约、海关协定和行业法规)…

git 已知问题 命令行调用 git 时可能存在环境变量投毒问题

本文记录一个我在 git 钩子唤起一个 C# dotnet 的进程,在此进程里面使用 Process.Start 执行 git 命令的时候,被 git 钩子环境变量投毒的问题核心代码非常简单,我只是使用 git add 命令而已var sourceFolder = @"C:\lindexi\Work\Source\";var processStartInfo =…

eSIM神器之ESTK记录

本文内容一部分引用“ 灯塔旅人”公众号文章内容。 先说原理: 原理 ⚡️普通手机:营业厅将信息写入实体SIM中,然后插入手机,实现相关功能; ⚡️eSIM手机:内置了eSIM芯片,可以直接扫码写入运营商提供的eSIM并支持切换; ⚡️ESTK:形象地比喻为将eSIM手机中eSIM芯片取出,…

Google TPU第六代TPUv6

简单谈谈Google TPUv6 根据Google TPU第六代的数据做了一些性能数据的对比,需要注意的是TPUv6当前应该是一个用于训推一体的单Die的版本,用于训练的V6p双Die版本应该会后期再发布. 需要注意的是在国内外都开始卷大模型推理价格的时候, TPU这样的东西对于提高ROI非常有帮助。快…

NPU 是什么芯片?AI芯片都有哪些?(下)

8. HPU(Holographic Processing Unit) 全息处理器。微软HoloLens是世界上首台独立的全息计算机设备,能够提供高清晰度的全息影像,其秘密在于HoloLens搭载的Holographic Processing Unit(全息处理单元,简称HPU),这是一款定制芯片,可以处理和交互不同传感器及Intel Atom…

NPU 是什么芯片?AI芯片都有哪些?(上)

NPU 是什么芯片?AI芯片都有哪些? NPU就是CPU功能细化的产物,就像当年只有CPU一样,发现处理一些简单的重复的任务,特别像游戏这种大量作图时只能用软件算法,效率及其低下,所以发展处的加速卡,发展成为GPU,现在处理一些ai学习算法时都是用GPU软件模拟,效率低下,功耗大…

Nginx 配置与实战

Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器,而且支持热部署,几乎可以做到 7 * 24 小时不间断运行,即使运行几个月也不需要重新启动,还能在不间断服务的情况下对软件版本进行热更新。性能是 Nginx 最重要的考量,其占用内存少、并发能力强、能支持高达 5w 个并发…

《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》 4本书推荐

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

乐心手环6S拆解

PART1:背景使用了1年多的乐心手环电量终于扛不住了,于是便拆开看看里面使用的方案还有堆叠设计PART2:拆解先来看看乐心手环产品功能和外观照片去掉手环的表带,通过热风枪调至200℃,对这面盖和四周均匀加热至烫手,然后从两边拆开即可分离面盖和主体元器件清单可以看到使用的…

请求响应

apipost插件,用于测试1. 简单参数 通过request手动获取请求参数: @RestController public class easy {@RequestMapping("/easyget")public String easyget(HttpServletRequest request) { //通过request获取参数String name = request.getParameter("name"…

【机器学习面试场景问题】

1、ResNet是什么? ‌ResNet(Residual Network,残差网络)是一种在深度学习领域中非常重要的卷积神经网络(CNN)架构。 ResNet的核心思想是残差学习,通过学习输入与输出的残差,简化优化任务。其关键结构是残差块,通过短连接(Skip Connection)直接跳过部分非线性层,将输…

Linux中删除第一列中指定分隔符最后的一个字段

001、[root@localhost test]# ls a.txt [root@localhost test]# cat a.txt ## 测试数据 00_3 8834 1b_kk ffaa 55_f3_34 8834 aa_bb_kk_44 44aa [root@localhost test]# sed s/\([^ ]*\)_[^ ]\+\t\(.*\)/\1\t\2/ a.txt ## 删除第一列的最后一个字…