Метод toString() в Java: переопределение и примеры
Вы создаёте объект Person, передаёте его в System.out.println(person) и вместо имени и возраста видите в консоли что-то вроде oop.Person@5e2a3193. Это не ошибка — так работает метод toString(), который класс унаследовал от Object и не переопределил.
Метод toString() в Java — это метод класса Object с сигнатурой public String toString(), который возвращает строковое представление объекта. Он есть у любого объекта, потому что все классы наследуются от Object. При создании нового класса метод toString() принято переопределять так, чтобы возвращаемая строка содержала имя класса и значения его основных полей.
Что возвращает toString() по умолчанию
Если класс не переопределяет метод, вызывается реализация из класса Object. Она устроена так:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
} То есть результат состоит из полного имени класса (вместе с пакетом), символа @ и хеш-кода объекта в шестнадцатеричном виде. Для класса Person из пакета oop это будет строка вида:
oop.Person@5e2a3193 Такая строка почти ничего не говорит о содержимом объекта, поэтому документация Java прямо рекомендует переопределять toString() во всех классах.
Важно
Число после @ — это не адрес объекта в памяти, а значение hashCode() в шестнадцатеричной записи. JVM может перемещать объекты при сборке мусора, а хеш-код при этом не меняется.
Переопределение метода toString()
Чтобы получить осмысленный вывод, в классе объявляют метод public String toString() с аннотацией @Override. Распространённый формат — ИмяКласса{поле=значение, ...}:
public class Person {
private String fullName;
private int age;
private boolean retired;
public Person(String fullName, int age, boolean retired) {
this.fullName = fullName;
this.age = age;
this.retired = retired;
}
@Override
public String toString() {
return "Person{"
+ "fullName='" + fullName + '\''
+ ", age=" + age
+ ", retired=" + retired
+ '}';
}
} Чтобы увидеть результат, достаточно передать объект в System.out.println — метод toString() будет вызван автоматически:
public class PersonExample4 {
public static void main(String[] args) {
Person person = new Person("Петров Иван Иванович", 56, false);
System.out.println(person);
}
} Результат выполнения программы:
Person{fullName='Петров Иван Иванович', age=56, retired=false} Правила хорошей реализации toString():
- указывайте имя класса и ключевые поля — строка должна помогать при отладке и чтении логов;
- не обязательно включать все поля: пароли, токены, большие коллекции и ссылки «назад» на родительский объект лучше исключить;
- метод не должен менять состояние объекта и выполнять тяжёлые вычисления;
- всегда ставьте
@Override— компилятор поймает опечатку в имени метода.
Можно вызвать метод и явно — System.out.println(person.toString()), — но внутри println() это избыточно. Кроме того, если person равен null, явный вызов бросит NullPointerException, а println(person) просто выведет null.
Когда toString() вызывается автоматически
Метод to string в Java редко вызывают вручную — чаще это делает сам язык или библиотека:
System.out.println(obj)иSystem.out.print(obj);- конкатенация строк:
"Клиент: " + person; String.valueOf(obj),String.format("%s", obj),StringBuilder.append(obj);- логгеры (SLF4J, Log4j) при подстановке объекта в сообщение;
- отладчик IDE, когда показывает значение переменной.
Разные способы получить строку по-разному обрабатывают null:
| Выражение | Объект не null | Объект равен null |
|---|---|---|
obj.toString() | результат toString() | NullPointerException |
String.valueOf(obj) | результат toString() | строка "null" |
"" + obj | результат toString() | строка "null" |
Objects.toString(obj, "нет данных") | результат toString() | значение по умолчанию |
Автогенерация toString(): IDE, Lombok, record
Писать toString() вручную для каждого класса не нужно:
- IntelliJ IDEA:
Alt+Insert(на macOSCmd+N) → toString(). Шаблон по умолчанию генерирует ровно тот формат, что в примере выше:Person{fullName='...', age=56, retired=false}. - Eclipse: Source → Generate toString()...
- Lombok: аннотация
@ToStringнад классом (или@Data, которая её включает). Отдельные поля исключаются через@ToString.Exclude.
Записи (record, стандартная возможность начиная с Java 16) получают toString() автоматически — вместе с equals() и hashCode():
public record Point(int x, int y) { }
System.out.println(new Point(1, 2)); // Point[x=1, y=2] Обратите внимание: у record формат с квадратными скобками, а не с фигурными, как у шаблона IntelliJ IDEA.
toString() для коллекций и массивов
Коллекции (ArrayList, HashMap и другие) toString() уже переопределяют и выводят элементы в квадратных или фигурных скобках. Но каждый элемент при этом печатается через свой собственный toString() — если он не переопределён в классе элемента, получится список вида [oop.Person@5e2a3193, oop.Person@6d06d69c].
Массивы, наоборот, toString() не переопределяют: System.out.println(numbers) выведет строку вида [I@1b6d3586. Чтобы напечатать элементы массива, используют метод Arrays.toString() — подробно он разобран в отдельном уроке Метод Arrays.toString() в Java.
Связь toString() с equals() и hashCode()
toString(), equals() и hashCode() — три метода класса Object, которые обычно переопределяют вместе. Но между ними есть разница: для equals() и hashCode() существует строгий контракт (равные объекты обязаны иметь равные хеш-коды), а для toString() — только рекомендация.
Есть и неочевидная связь: реализация toString() по умолчанию использует hashCode(). Если в классе переопределён hashCode(), но не toString(), то два равных объекта напечатаются одинаково, например oop.Person@1f и oop.Person@1f.
На чём чаще всего ошибаются
- Бесконечная рекурсия. Если
Departmentвыводит список своихEmployee, а каждыйEmployeeвыводит свойDepartment, вызов toString() закончитсяStackOverflowError. В одной из сторон связи выводите только идентификатор или имя. - Утечка чувствительных данных. toString() часто попадает в логи, поэтому пароли, номера карт и токены в него включать нельзя.
- Опечатка в имени метода. Метод
tostring()илиtoString(String s)— это новый метод, а не переопределение. Без@Overrideкомпилятор промолчит, и println() продолжит выводитьPerson@.... - Разбор результата toString() в логике программы. Формат строки предназначен для людей и может поменяться. Для сравнения используйте equals(), для сериализации — JSON-библиотеки или отдельные методы.
Часто задаваемые вопросы
Является ли число после @ в Person@5e2a3193 адресом объекта в памяти?
Нет. Это результат hashCode(), записанный в шестнадцатеричном виде. Хеш-код по умолчанию генерируется JVM и не совпадает с адресом объекта: сборщик мусора может переместить объект, а хеш-код останется прежним.
Обязательно ли переопределять toString()?
Нет, программа работает и без этого — в отличие от пары equals()/hashCode(), здесь нет контракта, нарушение которого ломает коллекции. Но документация Java рекомендует переопределять toString() во всех классах, потому что это сильно упрощает отладку и чтение логов.
Что возвращает toString() у enum?
По умолчанию — имя константы, то же значение, что и name(). toString() в enum можно переопределить, например, чтобы вернуть читаемое название, а метод name() объявлен final и всегда возвращает имя константы, как оно записано в коде.
Что будет, если переопределённый toString() вернёт null?
Код скомпилируется, а println() и конкатенация выведут строку null. Но это плохая практика: вызывающий код ожидает непустую строку, и obj.toString().length() упадёт с NullPointerException. Возвращайте хотя бы имя класса.
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии