JavaObject-oriented programmingInheritance and polymorphism

toString()

5 seconds read

Using default toString

The root Java class Object has the toString method to get the string representation of an object. If you'd like to get it, override this method in your class.

First, let's consider an example based on the default toString implementation provided by the Object class.

See the following example with the Account class. It has three fields and one constructor.

class Account {

    private long id;
    private String code;
    private Long balance;

    public Account(long id, String code, Long balance) {
        this.id = id;
        this.code = code;
        this.balance = balance;
    }
    
    // getters and setters
}

Let's create an instance of the class and get the string representation of the instance:
Account account = new Account(1121, "111-123", 400_000L);

String accString = account.toString(); // org.demo.example.Account@27082746

A string like org.demo.example.Account@27082746 is not we need. This result is based on the full class name and its hashcode. It's the default behavior of the toString method.

Overriding toString when declaring a class

To include fields in the string representation of an object, we should override the standard behavior of the toString method.

Here is another version of the Account class.

class Account {

    private long id;
    private String code;
    private Long balance;

    public Account(long id, String code, Long balance) {
        this.id = id;
        this.code = code;
        this.balance = balance;
    }
    
    // getters and setters

    @Override
    public String toString() {
        return "Account{id=" + id + ",code=" + code + ",balance=" + balance + "}";
    }
}

Let's create an instance of the class and get the string representation of the instance:
Account account = new Account(1121, "111-123", 400_000L);

String accString = account.toString(); // Account{id=1121,code=111-123,balance=400000}

The string representation is very useful for debugging and logging. You can use the method to display a string representation of an object in the standard input:

System.out.println(account.toString()); // or System.out.println(account) for short

Some modern IDEs, such as IntelliJ IDEA, allows generating the overridden toString method automatically. It's very convenient if your class has a lot of fields.

Overriding toString when subclassing

If you have a class hierarchy you also can override toString.

Here is a hierarchy of two classes:
  • Person with the single string field name;
  • Employee that extends Person and adds the field salary.

class Person {

    protected String name;

    public Person(String name) {
        this.name = name;
    }

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

class Employee extends Person {

    protected long salary;

    public Employee(String name, long salary) {
        super(name);
        this.salary = salary;
    }

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

It's a good practice to include the class name in the string representation when working with hierarchies.

Let's create two objects and prints them as strings.

Person person = new Person("Helena");
Employee employee = new Employee("Michael", 10_000);

System.out.println(person);   // Person{name=Helena}
System.out.println(employee); // Employee{name='Michael,salary=10000}

So, we can override the toString method for classes in a hierarchy.

Possible problems when overriding toString

Overriding the toString method looks very simple. But what if your class has another class as a type of a field? Sometimes it may cause an error.

See the following example with Person and Passport classes. We do not post getters and setters to make the code more compact.

class Person {

    private String name;
    private Passport passport;
    
    // getters and setters
    
    @Override
    public String toString() {
        return "Person{name='" + name + ",passport=" + passport + "}";
    }
}

class Passport {
    
    private String counrty;
    private String number;
    
    // getters and setters

    @Override
    public String toString() {
        return "Passport{country=" + country + ",number=" + number + "}";
    }
}

If a person has no passport (null), the string representation will contain the null.

Here is an example of two objects.

Passport passport = new Passport();
passport.setNumber("4343999");
passport.setCountry("Austria");

Person person = new Person();
person.setName("Michael");
person.setPassport(passport);

System.out.println(person);

This code prints:

Person{name=Michael,passport=null}
Person{name=Michael, passport=Passport{country=Austria, number=4343999}}

It works very well, no any problems here! But what if the passport has the backward reference to the person and tries to get the string representation of the person?

Let's add the following field to the class Passport and the corresponding setter:

private Person owner;

And modify the toString method as the follows:

@Override
public String toString() {
    return "Passport{country=" + country + ",number=" + number + ",owner=" + owner + "}";
}

When we create two objects let's set the owner to the passport:

passport.setOwner(person);

Now we get the big problem - the program tries to get the string representation of the person that includes the string representation of passport that includes the string representation of the person. It causes java.lang.StackOverflowError.

There are several ways to fix the arisen situation:
  • do not include fields represented by your classes in the toString method;
  • exclude the field in the toString method from one of the classes.

So, be careful when includes fields in the toString method. Consider references between classes. If you don't need any information, exclude it better. It will save you from fatal mistakes.
How did you like the theory?
Report a typo