Java toString() Method

1. Overview

Every class in Java is a child of Object class either directly or indirectly. Object class contains toString() method. As a result, we use this toString() method to get the string representation of an object.
In this tutorial, we will try to understand the default behavior of toString() method and learn how to change that behavior.


2. Default Behavior

Whenever we print the object reference, it invokes the toString() method internally. So, if we do not define toString() method in our class, then it invokes Object class toString() method.

The default behavior of the Object class toString() method is:

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


Now, Let us take an example of a Customer object:


public class Customer {
    private String firstName;
    private String lastName;
    // standard getters and setters. No toString() implementation

}


Now, if we try to print the customer object, it will use the Object class toString() implementation and the output will be as below:


com.baeldung.tostring.Customer@6d06d69c

3. Overriding Default Behavior

Looking at the above output, we can see that it does not give us much information about the Customer object. Generally, we would not be interested to know the hashcode of the object. That's why we need to override the default behavior to make it more meaningful.

Now, Let’s look at a couple different scenarios using objects to look at how we can override this behavior.

3.1. Primitive Types and Strings

Our Customer object is having String and primitive type of fields. We need to override the CustomerPrimitiveToString class toString() method to achieve more meaningful output:

public class CustomerPrimitiveToString extends Customer {
    private long balance;

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


Let’s see the results of calling the above toString() method:


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

3.2. Complex Java Objects

Let’s now consider a scenario where our Customer object also contains an order attribute that is of type Order. Our Order class has both String and primitive data type fields.

So, let’s override toString() again:


public class CustomerComplexObjectToString extends Customer {
    private Order orders;
    //standard setters and getters methods
    @Override
    public String toString() {
        return "Customer [orders=" + orders + ", getFirstName()=" + getFirstName()
          + ", getLastName()=" + getLastName() + "]";
    }      
}

Since "orders" is a complex java object, if we print the Customer object, it will print orders as Order@<hashcode>. To fix that we need to override Order class toString() method as well:

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 + "]";
    }
}

Let’s see the results of calling the above toString() method:


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

3.3. Array of Objects

Next, let’s change our Customer to have an array of Orders. If we just print our Customer object, without special handling for our orders object, it will print orders as Order;@<hashcode>.

To fix that let’s use Arrays.toString() for the orders field:


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

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

Let’s see the results of calling the above toString() method:

  @Test
  public void givenArray_whenToString_thenCustomerDetails() {
      final String CUSTOMER_ARRAY_TO_STRING 
        = "Customer [orders=[Order [orderId=A1111, desc=Game, value=0]], getFirstName()=Rajesh, getLastName()=Bhojwani]";     
      CustomerArrayToString customer = new CustomerArrayToString();
      customer.setFirstName("Rajesh");
      customer.setLastName("Bhojwani");
      Order[] orders = new Order[1];  
      orders[0] = new Order();
      orders[0].setOrderId("A1111");
      orders[0].setDesc("Game");
      orders[0].setStatus("In-Shiping");
      customer.setOrders(orders);         
      
      assertEquals(CUSTOMER_ARRAY_TO_STRING, customer.toString());
  }

3.4. Wrapper, Collection, StringBuffer Java Objects

When an object is made up entirely of wrappers, collections, or StringBuffers, no custom toString() implementation is required because these objects have already overridden the toString() method with meaningful representations:

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

}

Let’s see the results of calling the above toString() method:


  @Test
  public void givenWrapperCollectionStrBuffer_whenToString_thenCustomerDetails() {
      final String CUSTOMER_WRAPPER_COLLECTION_TO_STRING 
        = "Customer [score=8, orders=[Book, Pen], fullname=Bhojwani, Rajesh, getFirstName()=Rajesh, getLastName()=Bhojwani]";
      CustomerWrapperCollectionToString customer = new CustomerWrapperCollectionToString();
      customer.setFirstName("Rajesh");
      customer.setLastName("Bhojwani");
      customer.setScore(8);
      
      List<String> orders = new ArrayList<String>();
      orders.add("Book");
      orders.add("Pen");
      customer.setOrders(orders);
      
      StringBuffer fullname = new StringBuffer();
      fullname.append(customer.getLastName()+", "+ customer.getFirstName());
      customer.setFullname(fullname);
      
      assertEquals(CUSTOMER_WRAPPER_COLLECTION_TO_STRING, customer.toString());
  }

3.5. ReflectionToStringBuilder

Now, let's take a scenario where we have many Java objects along with nesting objects, it will be a time-consuming effort of implementing toString() method. Also, think of if we need to keep adding the fields and developer miss to update the toString() method with the latest object structure. So, we can make use of apache common lang Class ReflectionToStringBuilder.

ReflectionToStringBuilder assists in implementing Object.toString() method using reflection. This class uses reflection to determine the fields to append.

So now, instead of concatenating the string manually, let's add the below code in toString() method:



@Override
public String toString() {
    return ReflectionToStringBuilder.toString(this);
}

Let’s see the results of calling the above toString() method:


    @Test
    public void givenWrapperCollectionStrBuffer_whenReflectionToString_thenCustomerDetails() {
        final String CUSTOMER_REFLECTION_TO_STRING = "com.baeldung.string.tostring.CustomerReflectionToString";
        CustomerReflectionToString customer = new CustomerReflectionToString();
        customer.setFirstName("Rajesh");
        customer.setLastName("Bhojwani");
        customer.setScore(8);
        
        List<String> orders = new ArrayList<String>();
        orders.add("Book");
        orders.add("Pen");
        customer.setOrders(orders);
   
        StringBuffer fullname = new StringBuffer();
        fullname.append(customer.getLastName()+", "+ customer.getFirstName());
        customer.setFullname(fullname);
 
        assertTrue(customer.toString().contains(CUSTOMER_REFLECTION_TO_STRING));
    }

4. Conclusion

To summarize it, toString() method is a very important method. It is very useful while debugging code. So, to make it more useful, we need to put our own implementation using the above approaches.

You can find the source code for this article over on GitHub.

No comments: