Implementing toString

The toString method is widely implemented. It provides a simple, convenient mechanism for debugging classes during development. It's also widely used for logging, and for passing informative error messages to Exception constructors and assertions. When used in these informal ways, the exact format of toString is not part of the contract of the method, and callers should not rely on the exact format of the returned String.

The toString method may occasionally be used more formally, however. An example is a simple mechanism for translating an object into a well-defined textual form (toString) and back again (valueOf). In this case, it's particularly important to specify the exact form of such text in javadoc.

When implementing toString, StringBuilder can be used instead of the + concatenation operator, since the StringBuilder.append operation is slightly faster.

Example 1

import java.util.*;
import java.time.LocalDate;

public final class Truck {

  /** Simple test harness.  */
  public static void main(String... args){
    Truck truck = new Truck();
    System.out.println(truck);
  }

  /**
  * Intended only for debugging.
  *
  * <P>Here, the contents of every field are placed into the result, with
  * one field per line.
  */
  @Override public String toString() {
    StringBuilder result = new StringBuilder();
    String NL = System.getProperty("line.separator");
    
    result.append(this.getClass().getName() + " Object {" + NL);
    result.append(" Name: " + name + NL);
    result.append(" Number of doors: " + numDoors + NL);
    result.append(" Manufactured on: " + whenManufactured + NL );
    result.append(" Color: " + color + NL);
    //Note that Collections and Maps also override toString
    result.append(" Options: " + options + NL);
    result.append("}");

    return result.toString();
  }

  //..other methods elided
  
  // PRIVATE //

  /** Some toy data for illustrating toString(). */
  private String name = "Dodge";
  private Integer numDoors = 2;
  private LocalDate whenManufactured = LocalDate.of(2007, 9, 1);
  private String color = "Black";
  private List<String> options = List.of("Air Con", "Cruise Control");
}  

Example output:

Truck Object {
 Name: Dodge
 Number of doors: 2
 Year manufactured: Wed Aug 29 15:49:10 ADT 2007
 Color: Fuchsia
 Options: [Air Conditioning]
}

Example 2

This implementation uses reflection to inspect both field names and field values. Note that superclass fields do not contribute to this implementation.

import java.util.*;
import java.lang.reflect.Field;
import java.time.LocalDate;

public final class Van {

  /**
  * Build a Van object and display its textual representation.
  *
  * Note that the Collection classes have
  * their own implementation of <code>toString</code>, as exemplified here
  * by the List field holding the Options.
  */
  public static void main (String... args) {
    List<String> options = new ArrayList<>();
    options.add("Air Conditioning");
    options.add("Leather Interior");

    Van van = new Van("Dodge", 4, LocalDate.now(), "Blue", options);
    log(van);
  }

  public Van(
    String name,
    Integer numDoors,
    LocalDate whenManufactured,
    String color,
    List<String> options
  ){
    this.name = name;
    this.numDoors = numDoors;
    this.whenManufactured = whenManufactured;
    this.color = color;
    this.options = options;
  }

  //..other methods elided

  /**
  * Intended only for debugging.
  *
  * <P>Here, a generic implementation uses reflection to print
  * names and values of all fields <em>declared in this class</em>. Note that
  * superclass fields are left out of this implementation.
  *
  * <p>The format of the presentation could be standardized by using
  * a MessageFormat object with a standard pattern.
  */
  @Override public String toString() {
    StringBuilder result = new StringBuilder();
    String NL = System.getProperty("line.separator");

    result.append(this.getClass().getName());
    result.append(" Object {");
    result.append(NL);

    //determine fields declared in this class only (no fields of superclass)
    Field[] fields = this.getClass().getDeclaredFields();

    //print field names paired with their values
    for (Field field : fields) {
      result.append("  ");
      try {
        result.append(field.getName());
        result.append(": ");
        //requires access to private field:
        result.append(field.get(this));
      }
      catch (IllegalAccessException ex) {
        log(ex.toString());
      }
      result.append(NL);
    }
    result.append("}");

    return result.toString();
  }

  // PRIVATE 
  private String name;
  private Integer numDoors;
  private LocalDate whenManufactured;
  private String color;
  private List<String> options;
  
  private static void log(Object thing) {
    System.out.println(thing.toString());
  }
} 

Example output:

Van Object {
  fName: Dodge
  fNumDoors: 4
  fYearManufactured: Thu Sep 30 19:16:14 EDT 2004
  fColor: Blue
  fOptions: [Air Conditioning, Leather Interior]
}

Example 3

The WEB4J tool provides a utility method for implementing toString in a single line of code. Its implementation uses reflection.

import java.math.BigDecimal;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.model.Id;
import hirondelle.web4j.model.Check;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.model.Validator;

/**  Model Object for a Restaurant. */
public final class Resto {

  /**
  * Full constructor.
  *  
  * @param aId underlying database internal identifier (optional) 1..50 characters
  * @param aName of the restaurant (required), 2..50 characters
  * @param aLocation street address of the restaurant (optional), 2..50 characters
  * @param aPrice of the fish and chips meal (optional) $0.00..$100.00
  * @param aComment on the restaurant in general (optional) 2..50 characters
  */
  public Resto(
    Id aId, String aName, String aLocation, BigDecimal aPrice, String aComment
  ) throws ModelCtorException {
    fId = aId;
    fName = Util.trimPossiblyNull(aName);
    fLocation = Util.trimPossiblyNull(aLocation);
    fPrice = aPrice;
    fComment = Util.trimPossiblyNull(aComment);
    validateState();
  }
  
  public Id getId() { return fId; }
  public String getName() {  return fName; }
  public String getLocation() {  return fLocation;  }
  public BigDecimal getPrice() { return fPrice; }
  public String getComment() {  return fComment; }

  /** Intended for debugging only.  */
  @Override public String toString() {
    return ModelUtil.toStringFor(this);
  }

  //..elided

  //PRIVATE//
  private final Id fId;
  private final String fName;
  private final String fLocation;
  private final BigDecimal fPrice;
  private final String fComment;
 
  //..elided

}
 

Example output:

hirondelle.fish.main.resto.Resto {
Name: Cedars Eatery
Location: Water and Prince
Id: 6
Comment: Big portions
Price: 7.89
}

Example 4

QuoteField is a type safe enumeration. The return value of toString is used in the normal operation of the program - not just for logging. Note that it forms a pair with the valueFrom(String) method, since one formats the object into a String, and the other parses a String into an object.

package hirondelle.stocks.table;

/**
* Enumeration for the fields of the 
* {@link hirondelle.stocks.quotes.Quote} class.
* 
* Advantages to using this class as part of a table model :
* <ul>
* <li> can parse text which maps table columns to fields
* <li> can be used for column names
* <li> length of <tt>QuoteField.values()</tt> gives the column count
* </ul>
*/
public enum QuoteField { 

  Stock("Stock"),
  Price("Price"),
  Change("Change"),
  PercentChange("%Change"),
  Profit("Profit"),
  PercentProfit("%Profit");

  /**
  * Return a text representation of the <tt>QuoteField</tt>.
  *
  * Return values : <tt>Stock, Price, Change, %Change, Profit, %Profit</tt>.
  * @return value contains only letters, and possibly a percent sign.
  */
  @Override public String toString() { 
    return fName;  
  }

  /** 
  * Parse text into a <tt>QuoteField</tt>.
  * 
  * <P>The text is matched according to the value of {@link #toString()}, 
  * not from the symbolic name of the enumerated item.
  */
  public static QuoteField valueFrom(String aText){
    for (QuoteField quoteField: values()){
      if(quoteField.toString().equals(aText)) {
        return quoteField;
      }
    }
    throw new IllegalArgumentException("Cannot parse into a QuoteField: " + aText);
  }
  
  private final String fName;

  /**
  * @param aName only letters and percent sign are valid characters.
  */
  private QuoteField(String aName) { 
    fName = aName;
  }
} 

See Also :
String concatenation does not scale
Factory methods
Arrays as String