Object.toString() 是 Java 中最基础且最重要的方法之一,存在于所有 Java 对象的根类 java.lang.Object 中。理解其工作原理和最佳实践对编写高质量 Java 代码至关重要。

核心概念

1. 方法定义

public String toString()

2. 默认实现

Object 类中的实现:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

输出示例:com.example.MyClass@15db9742

3. 关键特性

  • 所有 Java 对象的通用方法
  • 主要用于提供对象的文本表示
  • 在调试、日志记录和字符串拼接中自动调用
  • 可以被(且通常应该被)重写以提供更有意义的信息

为什么需要重写 toString()

默认实现的局限性

public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// 使用默认 toString()
Person person = new Person("Alice", 30);
System.out.println(person); // 输出: com.example.Person@6d06d69c

重写后的优势

@Override
public String toString() {
    return "Person{name='" + name + "', age=" + age + "}";
}

// 输出: Person{name='Alice', age=30}

如何正确重写 toString()

基本重写模式

@Override
public String toString() {
    return "ClassName{" +
            "field1=" + field1 +
            ", field2='" + field2 + '\'' +
            ", field3=" + field3 +
            '}';
}

使用 StringBuilder(性能优化)

@Override
public String toString() {
    StringBuilder sb = new StringBuilder("Person{");
    sb.append("name='").append(name).append('\'');
    sb.append(", age=").append(age);
    sb.append(", address='").append(address).append('\'');
    sb.append('}');
    return sb.toString();
}

使用 Java 14+ 的 Record 类型

public record Person(String name, int age) {}

// 自动生成有意义的 toString()
Person person = new Person("Bob", 25);
System.out.println(person); // 输出: Person[name=Bob, age=25]

最佳实践与注意事项

1. 何时重写 toString()

  • 总是为值对象重写(如 DTO、实体类)
  • 为包含多个字段的类重写
  • 在需要调试或日志记录的类中重写
  • 避免为安全敏感类输出敏感数据

2. 内容规范

  • 包含类名(但可简化)
  • 包含所有重要字段
  • 格式统一(建议使用 ClassName{field=value} 格式)
  • 避免输出过多数据(特别是集合类)

3. 性能优化

// 不好: 字符串拼接创建多个临时对象
return "Person{name=" + name + ", age=" + age + "}";

// 好: 使用 StringBuilder(编译器会自动优化为 StringJoiner)
return new StringBuilder("Person{")
        .append("name=").append(name)
        .append(", age=").append(age)
        .append("}").toString();

// 更好: Java 8+ 的 StringJoiner
StringJoiner joiner = new StringJoiner(", ", "Person{", "}");
joiner.add("name=" + name);
joiner.add("age=" + age);
return joiner.toString();

4. 集合类的特殊处理

@Override
public String toString() {
    // 对于大型集合,限制输出数量
    int limit = 10;
    String elements = list.stream()
                          .limit(limit)
                          .map(Object::toString)
                          .collect(Collectors.joining(", "));
    
    if (list.size() > limit) {
        elements += ", ... and " + (list.size() - limit) + " more";
    }
    
    return "MyList{size=" + list.size() + ", elements=[" + elements + "]}";
}

5. 继承处理

public class Employee extends Person {
    private String department;
    
    @Override
    public String toString() {
        return super.toString().replace("}", "") + // 移除父类的结尾大括号
               ", department='" + department + '\'' +
               '}';
    }
}

常见错误

1. 忘记重写

// 调试时只能看到无意义的哈希值
MyObject obj = new MyObject();
System.out.println(obj); // 输出: com.example.MyObject@4554617c

2. 包含循环引用

class Parent {
    Child child;
    
    @Override
    public String toString() {
        return "Parent{child=" + child + "}"; // 危险!
    }
}

class Child {
    Parent parent;
    
    @Override
    public String toString() {
        return "Child{parent=" + parent + "}"; // 会导致 StackOverflowError
    }
}

解决方案:使用 Objects.toString() 避免递归

@Override
public String toString() {
    return "Parent{child=" + Objects.toString(child) + "}";
}

3. 输出敏感信息

// 危险: 输出密码
@Override
public String toString() {
    return "User{username='" + username + "', password='" + password + "'}";
}

解决方案:屏蔽敏感字段

@Override
public String toString() {
    return "User{username='" + username + "', password=******}";
}

4. 性能低下的实现

// 在循环中调用会导致性能问题
@Override
public String toString() {
    return "LargeCollection{" + allElements + "}"; // 可能包含数百万条目
}

使用场景

1. 调试和日志记录

logger.debug("Processing user: {}", user);
// 输出: Processing user: User{id=123, name='Alice'}

2. 字符串拼接

String message = "User details: " + user;
// 等同于: "User details: " + user.toString()

3. 容器类的文本表示

List<Person> people = Arrays.asList(
    new Person("Alice", 30),
    new Person("Bob", 25)
);

System.out.println(people);
// 输出: [Person{name='Alice', age=30}, Person{name='Bob', age=25}]

4. IDE 调试视图

在调试器中悬停在对象上时,会显示 toString() 的结果

高级技巧

1. 使用 ToStringBuilder(Apache Commons Lang)

@Override
public String toString() {
    return new ToStringBuilder(this)
            .append("name", name)
            .append("age", age)
            .append("address", address)
            .toString();
}

2. 使用 @ToString(Lombok)

import lombok.ToString;

@ToString
public class Person {
    private String name;
    private int age;
    private transient String password; // 不会被包含
}

// 自动生成: Person(name=Alice, age=30)

3. 格式化输出

@Override
public String toString() {
    return String.format("Person[name=%s, age=%d, salary=%.2f]", 
                         name, age, salary);
}

4. 多线程安全实现

// 对于可变对象
@Override
public String toString() {
    final String name;
    final int age;
    synchronized (this) {
        name = this.name;
        age = this.age;
    }
    return "Person{name='" + name + "', age=" + age + "}";
}

性能比较

方法 性能特点 适用场景
字符串连接 (+) 创建多个中间字符串对象 简单对象,字段少
StringBuilder 高效,单一线程安全 大多数情况
StringBuffer 线程安全但性能略低 多线程环境
String.format() 可读性好,性能较差 需要格式化输出时
StringJoiner Java8+ 推荐,高效易读 集合类或需要分隔符的情况
Objects.toString() 空安全,处理null值 包含可能为null的字段

总结

  1. 总是为重要类重写 toString():提供有意义的对象表示
  2. 包含关键信息但排除敏感数据:平衡信息量和安全性
  3. 保持格式一致:便于阅读和解析
  4. 考虑性能影响:避免在大型集合或性能关键代码中过度使用
  5. 利用现代工具:Lombok、Records 等可以简化实现
  6. 处理继承和组合:确保层次结构中的输出连贯
  7. 避免副作用:toString() 不应修改对象状态

正确实现 toString() 可以显著提高代码的可调试性和可维护性,是每个 Java 开发者应该掌握的基础技能。