为什么需要重写 toString()?
我们要明白 toString() 是什么。

在 Java 中,所有类都直接或间接继承自 Object 类。Object 类中提供了一个 toString() 方法,其原始定义如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这意味着,如果你不重写这个方法,当你尝试打印一个对象时(System.out.println(myObject)),你会得到类似下面这样的结果:
com.example.MyClass@15db9742
这个结果由三部分组成:
com.example.MyClass:类的全限定名。- 一个分隔符。
15db9742:该对象的哈希码的十六进制表示。
这个默认的字符串对于调试来说信息量很少,它无法告诉我们这个对象代表什么,它的状态(属性值)是什么。

重写 toString() 的目的就是:提供一个有意义的、人类可读的字符串表示,用来描述当前对象的状态。
这样做的好处非常明显:
- 调试和日志记录:在打印日志或使用调试器时,你能立刻看到对象的关键信息,而不是一串无意义的哈希码。
- 代码可读性:在开发和维护阶段,清晰的输出能极大地提高效率。
- 便捷输出:可以直接在
System.out.println()中使用对象,而无需手动拼接字符串。
如何重写 toString()?
重写 toString() 方法非常简单,只需要在你的类中定义一个符合以下签名的方法即可:
@Override
public String toString() {
// ... 在这里返回你想要的字符串
}
示例:一个简单的 Person 类
假设我们有一个 Person 类,包含 name 和 age 两个属性。

不重写 toString() 的情况:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 省略 getter 和 setter
}
测试代码:
public class Main {
public static void main(String[] args) {
Person person = new Person("张三", 30);
System.out.println(person); // 这里会自动调用 person.toString()
}
}
输出结果:
Person@15db9742
这个输出对我们理解 person 对象毫无帮助。
重写 toString() 的情况:
我们为 Person 类重写 toString() 方法。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 toString() 方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 省略 getter 和 setter
}
再次运行相同的测试代码:
public class Main {
public static void main(String[] args) {
Person person = new Person("张三", 30);
System.out.println(person);
}
}
输出结果:
Person{name='张三', age=30}
这个输出非常清晰,一眼就能看出这个 Person 对象的姓名和年龄,这就是重写 toString() 的威力。
最佳实践和技巧
手动拼接字符串虽然可行,但当对象属性很多时,会变得非常繁琐且容易出错,现代 Java 开发推荐以下几种更优雅的方式。
使用 StringBuilder (推荐)
手动拼接时,为了性能(避免创建多个中间字符串对象),应该使用 StringBuilder。
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Person{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
虽然代码变多了,但这是手动拼接时最规范、最高效的方式。
使用 Java 8 的 String.join() 和 Map
如果属性较多,可以先将它们放入一个 Map,然后使用 String.join 来拼接。
@Override
public String toString() {
// Map.of() 是 Java 9 引入的,用于方便地创建不可变 Map
Map<String, Object> map = Map.of(
"name", name,
"age", age
);
return "Person{" + String.join(", ", map.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.toList())) + "}";
}
这种方式更灵活,适合属性数量不固定或动态变化的场景。
使用 IDE 自动生成 (最推荐!)
几乎所有的现代 Java IDE(如 IntelliJ IDEA, Eclipse)都提供了自动生成 toString() 方法的功能,这是最快、最标准、最不容易出错的方式。
在 IntelliJ IDEA 中:
- 在类编辑器中,右键点击。
- 选择
Generate(或按Alt + Insert)。 - 选择
toString()。 - 在弹出的对话框中,选择你想要包含在字符串中的字段,然后点击
OK。
IDE 会自动为你生成一个格式良好、使用 StringBuilder 的 toString() 方法。
toString() 的约定
Java 官方文档(Object 类的 toString() 方法描述)对重写这个方法有一个不成文的约定,遵循这些约定会让你的代码更专业:
- 简洁:返回的字符串应该简洁且信息丰富。
- 可读:返回的字符串应该易于人类阅读。
- 包含所有相关信息:理想情况下,字符串应该包含足够的信息来唯一地表示对象的状态,这对于调试至关重要。
- 格式一致:格式为
ClassName{field1=value1, field2=value2, ...}是一个很好的选择。 - 不应包含敏感信息:绝对不要在
toString()方法中包含密码、信用卡号等敏感信息,因为日志、调试信息可能会被记录到不安全的地方。
| 项目 | 说明 |
|---|---|
| 为什么重写 | 提供一个有意义的、描述对象状态的字符串,方便调试、日志和阅读。 |
| 如何重写 | 在类中定义 public String toString() 方法。 |
| 核心思想 | 返回一个能清晰展示对象内部属性值的字符串。 |
| 最佳实践 | 使用 IDE 自动生成,它是最快、最规范的方式。 |
| 替代方案 | 手动使用 StringBuilder 进行拼接。 |
| 重要约定 | 简洁、可读、包含关键信息、不包含敏感信息。 |
养成一个好习惯:在创建任何用于承载数据的类(POJO/DTO)时,都第一时间重写 toString() 方法,这会在你未来的开发工作中节省大量的调试时间。
