1. 概述

在 Java 中,所有类都直接或间接继承自 Object 类,因此任何对象都可以调用 toString() 方法来获取其字符串表示形式。

本文将带你了解 toString() 的默认行为,并教你如何根据实际需要对其进行重写,使输出更具可读性和调试价值。

2. 默认行为

当我们打印一个对象引用时,实际上是在调用该对象的 toString() 方法。如果我们没有显式地在类中定义这个方法,那么就会使用 Object 类提供的默认实现:

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

举个例子,我们定义一个简单的 Customer 类(未重写 toString()):

public class Customer {
    private String firstName;
    private String lastName;
    // 标准 getter 和 setter,无 toString()
}

当我们打印这个类的实例时,输出会类似这样:

com.baeldung.tostring.Customer@6d06d69c

结论:默认的 toString() 只输出类名和哈希码,对调试和日志记录几乎没有帮助。

3. 重写默认行为

上面的输出显然不够直观。我们更关心的是对象内部的字段值,而不是它的内存地址。

通过重写 toString() 方法,我们可以自定义对象的字符串表示形式,使其更具有业务含义和调试价值。

接下来我们通过几个常见场景来演示如何重写 toString()

4. 处理基本类型和字符串字段

假设我们的 Customer 对象包含基本类型和字符串字段,比如余额(balance):

public class CustomerPrimitiveToString extends Customer {
    private long balance;

    @Override
    public String toString() {
        return "Customer [balance=" + balance + ", getFirstName()=" + getFirstName()
          + ", getLastName()=" + getLastName() + "]";
    }
}

单元测试如下:

@Test
public void givenPrimitive_whenToString_thenCustomerDetails() {
    CustomerPrimitiveToString customer = new CustomerPrimitiveToString();
    customer.setFirstName("Rajesh");
    customer.setLastName("Bhojwani");
    customer.setBalance(110);
    assertEquals("Customer [balance=110, getFirstName()=Rajesh, getLastName()=Bhojwani]", 
      customer.toString());
}

✅ 输出结果更直观,便于调试。

5. 处理复杂对象字段

如果 Customer 包含一个复杂对象,比如 Order,我们不仅要重写 CustomertoString(),也要确保 Order 有良好的字符串表示。

public class CustomerComplexObjectToString extends Customer {
    private Order order;

    @Override
    public String toString() {
        return "Customer [order=" + order + ", getFirstName()=" + getFirstName()
          + ", getLastName()=" + getLastName() + "]";
    }      
}

Order 类也需要重写:

public class Order {
    private String orderId;
    private String desc;
    private long value;
    private String status;
 
    @Override
    public String toString() {
        return "Order [orderId=" + orderId + ", desc=" + desc + ", value=" + value + "]";
    }
}

测试代码:

@Test
public void givenComplex_whenToString_thenCustomerDetails() {
    CustomerComplexObjectToString customer = new CustomerComplexObjectToString();    
    customer.setFirstName("Rajesh");
    customer.setLastName("Bhojwani");
    
    Order order = new Order();
    order.setOrderId("A1111");
    order.setDesc("Game");
    order.setStatus("In-Shipping");
    customer.setOrder(order);
        
    assertEquals("Customer [order=Order [orderId=A1111, desc=Game, value=0], " +
      "getFirstName()=Rajesh, getLastName()=Bhojwani]", customer.toString());
}

⚠️ 注意:如果 Order 没有重写 toString(),输出会变成 Order@<hashcode>,毫无意义。

6. 处理对象数组

如果 Customer 中包含一个 Order 数组,直接打印会输出类似 [Lcom.baeldung.Order;@hashcode 这样的内容。

为了解决这个问题,我们可以使用 Arrays.toString()

public class CustomerArrayToString extends Customer {
    private Order[] orders;

    @Override
    public String toString() {
        return "Customer [orders=" + Arrays.toString(orders) 
          + ", getFirstName()=" + getFirstName()
          + ", getLastName()=" + getLastName() + "]";
    }    
}

测试代码:

@Test
public void givenArray_whenToString_thenCustomerDetails() {
    CustomerArrayToString customer = new CustomerArrayToString();
    customer.setFirstName("Rajesh");
    customer.setLastName("Bhojwani");

    Order order = new Order();
    order.setOrderId("A1111");
    order.setDesc("Game");
    order.setValue(0);

    customer.setOrders(new Order[] { order });         
    
    assertEquals("Customer [orders=[Order [orderId=A1111, desc=Game, value=0]], " +
      "getFirstName()=Rajesh, getLastName()=Bhojwani]", customer.toString());
}

✅ 使用 Arrays.toString() 可以优雅地展示数组内容。

7. 处理包装类、集合和 StringBuffer

对于以下类型的字段:

  • 包装类(如 Integer, Long
  • 集合类(如 List, Map
  • StringBuffer / StringBuilder

这些类已经重写了 toString(),所以我们无需手动处理:

public class CustomerWrapperCollectionToString extends Customer {
    private Integer score;
    private List<String> items;
    private StringBuffer fullname;
  
    @Override
    public String toString() {
        return "Customer [score=" + score + ", items=" + items + ", fullname=" + fullname
          + ", getFirstName()=" + getFirstName() + ", getLastName()=" + getLastName() + "]";
    }
}

测试代码:

@Test
public void givenWrapperCollectionStrBuffer_whenToString_thenCustomerDetails() {
    CustomerWrapperCollectionToString customer = new CustomerWrapperCollectionToString();
    customer.setFirstName("Rajesh");
    customer.setLastName("Bhojwani");

    customer.setScore(8);
    customer.setItems(Arrays.asList("Book", "Pen"));

    StringBuffer fullname = new StringBuffer();
    fullname.append("Bhojwani, Rajesh");
    customer.setFullname(fullname);

    assertEquals("Customer [score=8, items=[Book, Pen], fullname=Bhojwani, Rajesh, getFirstName()=Rajesh, "
      + "getLastName()=Bhojwani]", customer.toString());
}

✅ 这些类型可以直接使用,无需额外处理。

8. 总结

在这篇文章中,我们探讨了 Java 中 toString() 方法的默认行为及其重写的最佳实践。

  • ✅ 重写 toString() 是一种良好的编程习惯,有助于调试和日志输出
  • ✅ 对于复杂对象、数组、集合等,要特别注意其字符串表示方式
  • ⚠️ 如果不重写,调试时输出的信息几乎无用

记住一句话:

toString() 不只是给程序员看的,更是给未来的自己看的。写得好,调试省一半时间。


原始标题:Java toString() Method | Baeldung