Каждый класс в Java является дочерним прямо или косвенно по отношению к классу Object. И поскольку класс Object содержит метод toString(), мы можем вызвать toString() в любом экземпляре и получить его строковое представление.
В этой статье мы рассмотрим поведение toString() по умолчанию и узнаем, как его изменить.
Поведение по умолчанию
Всякий раз, когда мы печатаем ссылку на объект, она вызывает внутренний метод toString(). Итак, если мы не определяем метод toString() в нашем классе, то вызывается Object.toString().
Метод toString() в Object довольно общий:
public String toString() {
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
Чтобы увидеть, как это работает, давайте создадим объект Car, который мы будем использовать на протяжении всей нашей статьи:
public class Car {
private String model;
private String color;
// здесь стандартные геттеры и сеттеры. Нет реализации метода toString()
}
Теперь, если мы попытаемся напечатать наш объект Car, будет вызван Object.toString(), и результат будет похож на что-то подобное:
com.test.tostring.Car@6d06d54c
Переопределение поведения по умолчанию
Глядя на приведенный выше вывод, мы видим, что он не дает нам много информации о содержимом нашего объекта Car. Как правило, нас интересует не знание хэш-кода объекта, а скорее содержимое атрибутов нашего объекта.
Переопределяя поведение метода toString() по умолчанию, мы можем сделать вывод метода более значимым.
Теперь давайте рассмотрим несколько различных сценариев с использованием объектов, чтобы увидеть, как мы можем переопределить это поведение по умолчанию.
Примитивные типы и строки
Наш объект Car имеет как строковые, так и примитивные атрибуты. Нам нужно переопределить метод toString() для достижения более значимого результата:
public class CarPrimitiveToString extends Car {
private long weight;
@Override
public String toString() {
return "Car [weight=" + weight + ", getModel()=" + getModel()
+ ", getColor()=" + getColor() + "]";
}
}
Давайте посмотрим, что мы получим, когда вызовем toString():
@Test
public void givenPrimitive_whenToString_thenCarDetails() {
CarPrimitiveToString car = new CarPrimitiveToString();
car.setModel("Ford");
car.setColor("White");
car.setWeight(1300);
assertEquals("Car [weight=1300, getModel()=Ford, getColor()=White]",
car.toString());
}
Сложные объекты Java
Давайте теперь рассмотрим сценарий, в котором наш объект Car также содержит атрибут order, имеющий тип Order. Наш класс Order содержит поля как строкового, так и примитивного типа данных.
Итак, давайте снова переопределим toString():
public class CarComplexObjectToString extends Car {
private Order order;
//здесь стандартные геттеры и сеттеры
@Override
public String toString() {
return "Car [order=" + order + ", getModel()=" + getModel()
+ ", getColor()=" + getColor() + "]";
}
}
Order – это сложный объект, поэтому если мы просто напечатаем экземпляр Car, не переопределяя метод toString() в нашем классе Order, он будет печатать как Order@<hashcode>.
Чтобы исправить это, давайте также переопределим toString() по порядку:
Чтобы исправить это, давайте также переопределим toString() по порядку:
public class Order {
private String orderId;
private String desc;
@Override
public String toString() {
return "Order [orderId=" + orderId + ", desc=" + desc + "]";
}
}
Теперь давайте посмотрим, что произойдет, когда мы вызовем метод toString() для нашего объекта Car, который содержит атрибут order:
@Test
public void givenComplex_whenToString_thenCarDetails() {
CarComplexObjectToString car = new CarComplexObjectToString();
// .. здесь заполняем car
Order order = new Order();
order.setOrderId("4512");
order.setDesc("car order");
car.setOrders(order);
assertEquals("Car [order=Order [orderId=4512, desc=car order], " +
"getModel()=Ford, getColor()=White]", car.toString());
}
Массив объектов
Далее давайте изменим класс Car, чтобы у него был массив Order. Если мы просто напечатаем наш объект Car, без специальной обработки для нашего объекта orders, он будет печатать Order;@<хэш-код>.
Чтобы исправить это, давайте воспользуемся Arrays.toString() для поля orders:
public class CarArrayToString extends Car {
private Order[] orders;
@Override
public String toString() {
return "Car [orders=" + Arrays.toString(orders)
+ ", getModel()=" + getModel()
+ ", getWhite()=" + getWhite() + "]";
}
}
Давайте посмотрим на результат вызова вышеупомянутого метода toString():
@Test
public void givenArray_whenToString_thenCarDetails() {
CarArrayToString car = new CarArrayToString();
// .. здесь заполняем car и order
car.setOrders(new Order[] { order });
assertEquals("Car [orders=[Order [orderId=4512, desc=car order]], " +
"getModel()=Ford, getColor()=White]", car.toString());
}
Wrappers, Collections и StringBuffers
Когда объект состоит из врапперов, коллекций или StringBuffer-ов, пользовательская реализация toString() не требуется, поскольку эти объекты уже переопределили метод toString() нужными представлениями:
public class CarWrapperCollectionToString extends Car {
private Integer year; // Wrapper class
private List<String> orders; // Collection
private StringBuffer pilot; // StringBuffer
@Override
public String toString() {
return "Car [year=" + year + ", orders=" + orders + ", pilot=" + pilot
+ ", getModel()=" + getModel() + ", getColor()=" + getColor() + "]";
}
}