前言
当小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
类到Student
的toString
继承关系。
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
类中没有重写该方法,所以调用了超级父类Object
的toString()
方法。
小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));
总结
-
toString()
现象分析:打印Student
对象输出的是类名和哈希码的组合,这是因为Student
类没有重写Object
类的toString()
方法。 -
toString()
原理追踪:-
Object
祖传代码揭秘:所有Java类都直接或间接继承了Object
类,Object
类的toString()
方法返回类名、@
符号和哈希码的无符号十六进制表示形式。 -
底层调用链揭秘:
System.out.println(obj)
语句实际调用了String.valueOf(obj)
→obj.toString()
,最终调用Java对象的toString()
方法。
-
-
答疑解惑:打印对象时输出的内容是类名和哈希码,哈希码是根据对象内部状态计算的整数,与引用地址不同。
-
实战指南:
-
必备要素清单:类型标识、关键字段展示、时间格式化、敏感信息处理、循环引用处理。
-
高效编程技巧:
IDE
自动生成、Lombok
魔法、手写高性能版。
-
-
避坑预警:
-
反面教材警示:泄露敏感信息会导致安全事故隐患,无限递归会导致栈溢出。
-
测试验证秘籍:使用
JUnit
断言检查输出格式。
-
-
彩蛋时刻:在工作中可通过Spring Boot让对象“说话”。
总之,掌握toString()
方法对Java开发者至关重要,它可以帮助我们更好地调试代码、输出对象信息,提高开发效率。