Skip to content

Latest commit

 

History

History
159 lines (112 loc) · 8.06 KB

File metadata and controls

159 lines (112 loc) · 8.06 KB

java.lang.Object#toString

Введение

Как можно догадаться из названия, данный метод позволяет получить некоторое строковое представление объекта, у которого он вызывается.

Если не переопределять данный метод, то строковое представление объекта будет результатом выполнения следующего кода:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

Т.е по умолчанию результатом будет имя класса и его hashCode в hexadecimal представлении, разделенные символом @.

Именно поэтому в JavaDoc рекомендуется этот метод переопределять - кому охота вместо человекочитаемой информации видеть hash-код и имя класса?

Хорошо реализованный toString помогает еще и при отладке кода, так как в логе в таком случае печатаются легкочитаемые и информативные строки, показывающие что это за объект, какие были поля и какие стали при выполнении программы.

Помните, что если вы переопределяете метод 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, то правильным было бы проконтролировать то, что у такого поля имеется get-метод.

И действительно, если мы включаем поле в toString, который является публичным методом, то такое поле как минимум логично сделать доступным на чтение. Ведь его значение все равно попадает в результат toString.

Какой смысл убирать или не писать get-метод на какое-нибудь поле name или age, если мы эти значения отдаем в результате вызова toString?

В конце повествования разберем еще вот такой пример:

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 только необходимую и достаточную информацию об объекте, убирайте лишнее и не нужное.
  • Если вы включили в toString какие-то внутренние объекты - убедитесь, что у них тоже реализован toStirng, в противном случае - сфорируйте строкове представление объекта самостоятельно или вообще его не включайте.
  • Контролируйте то, что вы включаете в реализацию toString, чтобы не получить java.lang.StackOverflowError.
  • Старайтесь не включать важные бизнес-данные в toString, если не хотите, чтобы это попало в логи.
  • Старайтесь не строить свою логику и работу программы на результате вызова toStirng.

Помните, что большинство IDE сейчас легко сгенерируют вам toString, чтобы вы не писали его вручную.

Также, существуют сторонние проекты, которые берут кодогенерацию на себя, например, проект lombok.