Skip to content

Latest commit

 

History

History
179 lines (124 loc) · 9.89 KB

toString.md

File metadata and controls

179 lines (124 loc) · 9.89 KB

java.lang.Object#toString

Введение

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

У java.lang.Object этот метод определен как:

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. Если вы включаете какое-либо поле объекта в 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.