Как можно догадаться из названия, данный метод позволяет получить некоторое строковое представление объекта, на котором он вызывается.
У java.lang.Object
этот метод определен как:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Т.е по-умолчанию результатом будет имя класса и его hashCode
в hexadecimal представлении, разделенные символом @
.
Именно поэтому в JavaDoc
рекомендуется этот метод переопределять: никому не хочется вместо человекочитаемой информации видеть hash
-код и имя класса!
Хорошо реализованный toString
помогает и при отладке кода, так как в логе в таком случае печатаются легкочитаемые и информативные строки, показывающие что это за объект и что у него было за состояние на момент вызова.
Помните, что если вы переопределяете метод toString
, то возвращаемая строка должна содержать всю значимую информацию объекта.
Для примера рассмотрим следующий класс и переопределим метод toString
:
public class Person {
private int age;
private int number;
private double salary;
private String name;
private CarKey carKey;
public Person(int age, int number, String name, double salary, CarKey carKey) {
this.age = age;
this.number = number;
this.name = name;
this.salary = salary;
this.carKey = carKey;
}
@Override
public String toString() {
return String
.format("Person name: %s, age: %d, number: %s, salary: %4.2f, carKey: %s",
name, age, number, salary, carKey);
}
}
class CarKey {
private int key;
public CarKey(int key) {
this.key = key;
}
}
Создадим объект и распечатаем его в консоль:
public static void main(String[] args) {
System.out.println(new Person(27, 8, "Aleksandr", 200000, new CarKey(14)));
}
Полученный результат:
Person name: Aleksandr, age: 27, number: 8, salary: 200000.00, carKey: examples.CarKey@2f92e0f4
Без переопределения toString
у класса CarKey
его объект снова выведет нечеловекочитаемую информацию, что должно навести на мысль: а так ли нужен вывод CarKey
?
Если да, мы понимаем, что CarKey
обязателен, это значимая информация для строкового представления класса Person
, то необходимо либо переопределить toString
у CarKey
, либо вручную, например, с помощью get
-методов, сформировать строковое представление объекта класса CarKey
.
Еще один важный момент - это наличие и отсутствие get
-методов для полей, которые входят в вывод toString
.
Если вы включаете какое-либо поле объекта в toString
, то правильно было бы проконтролировать то, что у такого поля имеется get
-метод.
И действительно, если мы включаем поле в toString
, который является публичным методом, то такое поле как минимум логично сделать доступным на чтение. Ведь его значение все равно попадает в результат toString
.
Не имеет смысла убирать или не писать get
-метод на поле name
или age
, если эти значения все равно будут показаны в результате вызова toString
.
Ни в коем случае не стоит включать в toString
пароли и важные данные о пользователе или проводимой операции!
Вопрос:
Как вы думаете, определен ли и если определен, то как метод toString
у классов-оберток в Java
? Например, java.lang.Integer
?
Ответ:
Метод toString
у классов-оберток переопределен и всегда возвращает строковое представление значения примитива:
System.out.println(Integer.valueOf(10)); // 10
В конце повествования разберем еще вот такой пример:
public class Test {
public static void main(String[] args) {
Test2 test2 = new Test2();
Test1 test1 = new Test1(test2);
test2.setTest1(test1);
System.out.println(test1);
}
}
class Test1 {
Test2 test2;
public Test1(Test2 test2) {
this.test2 = test2;
}
@Override
public String toString() {
return "Test1{ test2=" + test2 + '}';
}
}
class Test2 {
Test1 test1;
public Test2() {
}
public void setTest1(Test1 test1) {
this.test1 = test1;
}
@Override
public String toString() {
return "Test2{ test1=" + test1 + '}';
}
}
У нас два класса, каждый из которых содержит ссылку на другой класс.
Мы переопределяем toStirng
так, как показано в коде выше.
Как вы думаете, что получится?
А получится:
java.lang.StackOverflowError
Как это произошло: System.out.println
вызывает у объекта test1
метод toString
, в методе toString
у test1
происходит вызов toString
у объекта test2
, внутри которого уже снова идет обращение к toString
у test1
. В результате мы получаем зацикленность - мы ходим по кругу, вызывая toString
, пока стек вызовов не переполнится.
Змей Уроборос снова укусил себя за хвост.
Дабы избежать таких ситуаций необходимо смотреть на то, что вы включаете в реализацию toStirng
.
Старайтесь избегать написания кода, который завязан на результат работы toString
. Метод не дает никаких гарантий о том, в каком формате и виде будет сформирована строка, строить свою логику вокруг этого не самая лучшая идея. Исключением из этого правила может быть разве что работа с примитивами и классами-обертками.
Если планируется использовать строковое представление класса, то необходимо переопределить метод toString
.
Помните, что важно включать в такую реализацию только необходимую и достаточную информацию об объекте, убирая лишнее и не нужное.
Плохим тоном считается создание огромных строковых представлений, в которых половина информации не имеет значения или является секретной, например, пароль пользователя.
Если у поля нет get
-метода, то, задайтесь вопросом: а так ли нужно включать такое поле в строковое представление объекта?
При включении в toString
поля, принадлежащего к ссылочному типа, убедитесь, что у этого типа также переопределен toString
, иначе вам придется вручную формировать строковое представление объекта. А лучше и вовсе задуматься о том, чтобы отказаться от включения его в toString
реализацию класса.
Контролируйте то, что вы включаете в реализацию toString
, помните о возможности циклического вызова, который неизбежно приведет к java.lang.StackOverflowError
.
Старайтесь не строить свою логику и работу программы на результате вызова toStirng
!
Помните, что большинство IDE сейчас легко сгенерируют вам
toString
, чтобы вы не писали его вручную.Также, существуют сторонние проекты, которые берут кодогенерацию на себя, например, проект lombok.