Рубрики
Без рубрики

Методы объектов Java: toString()

Автор оригинала: Adam McQuistan.

Вступление

В этой статье я начну серию статей, описывающих часто забываемые методы базового класса объектов языка Java. Ниже приведены методы базового объекта Java, которые присутствуют во всех объектах Java из-за неявного наследования объекта. Ссылки на каждую статью этой серии включаются для каждого метода по мере публикации статей.

  • toString (вы здесь)
  • getClass
  • равняется
  • Хэш-код
  • клон
  • завершать
  • ждать и уведомлять

В следующих разделах я опишу, что это за методы, их базовые реализации и как их переопределять при необходимости. В центре внимания этой первой статьи находится метод toString () , который используется для предоставления строкового представления, идентифицирующего экземпляр объекта и передающего его содержание и/или значение в удобочитаемой форме.

Метод toString()

На первый взгляд метод toString() может показаться довольно бесполезным методом, и, честно говоря, его реализация по умолчанию не очень полезна. По умолчанию метод toString() вернет строку, в которой указано имя класса, за которым следует знак@, а затем шестнадцатеричное представление ячейки памяти, которой был назначен экземпляр объекта.

Чтобы помочь мне в обсуждении вездесущих методов объектов Java, я буду работать с простым классом Person , определенным следующим образом:

package com.adammcquistan.object;

import java.time.LocalDate;

public class Person {
    private String firstName;
    private String lastName;
    private LocalDate dob;
    
    public Person() {}
    
    public Person(String firstName, String lastName, LocalDate dob) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.dob = dob;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public LocalDate getDob() {
        return dob;
    }

    public void setDob(LocalDate dob) {
        this.dob = dob;
    }
}

Наряду с этим классом у меня есть элементарный Основной класс для запуска примеров, показанных ниже, чтобы представить функции базовой реализации toString() .

package com.adammcquistan.object;

import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        Person me = new Person("Adam", "McQuistan", LocalDate.parse("1987-09-23"));
        Person me2 = new Person("Adam", "McQuistan", LocalDate.parse("1987-09-23"));
        Person you = new Person("Jane", "Doe", LocalDate.parse("2000-12-25"));
        System.out.println("1. " + me.toString());
        System.out.println("2. " + me);
        System.out.println("3. " + me + ", " + you);
        System.out.println("4. " + me + ", " + me2);
}

Результат выглядит следующим образом:

Первое, что следует упомянуть, это то, что выходные данные для первой и второй строк идентичны , что показывает, что при передаче экземпляра объекта таким методам, как print , println , printf , а также регистраторам, неявно вызывается метод toString () . Кроме того, этот неявный вызов toString() также происходит во время объединения, как показано в выводе строки 3.

Хорошо, теперь пришло время мне высказать свое личное мнение, когда дело доходит до лучших практик программирования на Java. Что вам кажется потенциально тревожным в строке 4 (на самом деле, в любом выводе, если на то пошло)?

Надеюсь, вы отвечаете на вопрос примерно в таком духе: “Ну, Адам, приятно, что в выходных данных указано имя класса, но что, черт возьми, мне делать с этим дурацким адресом памяти?”.

И я бы ответил: “Ничего!”. Это на 99,99% бесполезно для нас, программистов. Гораздо лучшей идеей было бы для нас переопределить эту реализацию по умолчанию и предоставить что-то действительно значимое, например:

public class Person {
    // omitting everyting else remaining the same

    @Override
    public String toString() {
        return "";
    }
}

Теперь, если я повторю предыдущий основной класс, я получу следующий значительно улучшенный результат:

1. 
2. 
3. , 
4. , 

БОЖЕ! Что-то, что я могу прочесть! Благодаря этой реализации у меня теперь есть реальный шанс действительно понять, что происходит в файле журнала. Это особенно полезно, когда техподдержка кричит о странном поведении, связанном с экземплярами людей в программе, за которую я на крючке.

Предостережения по реализации и использованию toString()

Как показано в предыдущем разделе, реализация информативного метода toString() в ваших классах является довольно хорошей идеей, поскольку она обеспечивает способ осмысленной передачи содержимого и идентичности объекта. Однако бывают моменты, когда вам захочется применить несколько иной подход к их реализации.

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Например, предположим, что у вас есть объект, который просто содержит слишком много состояний для упаковки в выходные данные метода toString() или когда объект в основном содержит набор методов утилит. В этих случаях часто рекомендуется выводить простое описание класса и его намерений.

Рассмотрим следующий бессмысленный служебный класс, который находит и возвращает самого старого человека из списка объектов People.

public class OldestPersonFinder {
    public List family;
    
    public OldestPersonFinder(List family) {
        this.family = family;
    }

    public Person oldest() {
        if (family.isEmpty()) {
            return null;
        }
        Person currentOldest = null;
        for (Person p : family) {
            if (currentOldest == null || p.getDob().isAfter(currentOldest.getDob())) {
                currentOldest = p;
            }
        }
        return currentOldest;
    }

    @Override
    public String toString() {
        return "Class that finds the oldest Person in a List";
    }
}

В этом случае было бы не очень полезно перебирать всю коллекцию объектов Person в семействе Списке члена экземпляра и создавать какую-то смехотворно большую строку для возврата, представляющую каждого Person . Вместо этого гораздо более целесообразно возвращать строку, описывающую намерения класса, который в данном случае должен найти Человека , который является самым старшим.

Еще одна вещь, которую я хотел бы настоятельно рекомендовать, – это убедиться, что вы предоставляете доступ ко всей информации, относящейся к данным вашего класса, которые вы включаете в выходные данные вашего метода toString () .

Скажем, например, я не предоставил метод получения для своего Человека класса dob участника в тщетной попытке сохранить возраст человека в секрете. К сожалению, пользователи моего класса Person в конечном итоге поймут, что они могут просто проанализировать выходные данные метода toString() и получить данные, которые они ищут таким образом. Теперь, если я когда-нибудь изменю реализацию toString () , я почти наверняка нарушу их код. С другой стороны, позвольте мне сказать, что, как правило, плохая идея-анализировать вывод объекта toString() именно по этой причине.

Вывод

В этой статье описано использование и значение часто забываемого метода toString() базового класса объектов Java. Я объяснил поведение по умолчанию и высказал свое мнение о том, почему я считаю, что лучше всего реализовать поведение, специфичное для вашего класса.

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