Действительно ли стоит реализовывать toString () для классов сущностей? - PullRequest
10 голосов
/ 03 февраля 2011

Настоятельно рекомендуется переопределить (реализовать) метод toString() класса.

  • В документации Java API написано "Рекомендуется, чтобы все подклассы переопределяли этот метод.".
  • Bloch, в Effective Java есть пункт «Всегда переопределять toString». И только дурак противоречит Блоху, верно?

Однако я сомневаюсь в этом совете: действительно ли стоит реализовать toString() для классов сущностей ?


Я постараюсь изложить свои рассуждения.

  1. Объект имеет уникальный идентификатор; он никогда не совпадает с другим объектом, даже если два объекта имеют эквивалентные значения атрибута. То есть (для ненулевого x ) для класса сущности применяется следующий инвариант (по определению):

    x.equals(y) == (x == y)

  2. Метод toString() возвращает строку, которая «текстуально представляет» его объект (в словах Java API).

  3. A хорошее представление охватывает основы объекта, поэтому, если два представления различны, они являются представлениями разных (неэквивалентных) объектов, и наоборот, если два представления эквивалентны, они являются представлениями эквивалентных объектов. Это предполагает следующий инвариант для хорошего представления (для ненулевого x , y ):

    x.toString().equals(y.toString()) == x.equals(y)

  4. Таким образом, для субъектов мы ожидаем x.toString().equals(y.toString()) == (x == y) то есть каждый объект сущности должен иметь уникальное текстовое представление, которое возвращает toString(). Некоторые классы сущностей будут иметь уникальное имя или поле числового идентификатора, поэтому их метод toString() может возвращать представление, которое включает это имя или числовой идентификатор. Но в целом метод toString() не имеет доступа к такому полю.

  5. Без уникального поля для сущности лучшее, что может сделать toString(), - это включить поле, которое вряд ли будет одинаковым для разных объектов. Но это именно требование System.identityHashCode(), которое Object.toString() предоставляет.

  6. Итак, Object.toString() нормально для объекта-сущности, у которого нет элементов данных, но для большинства классов вы хотели бы включить их в текстовое представление, верно? Фактически, вы бы хотели включить всех из них: если тип имеет (ненулевой) элемент данных x , вы бы хотели включить x.toString() в представление.

  7. Но это создает проблему для элементов данных, которые содержат ссылки на другие объекты: это ассоциации . Если объект Person имеет элемент данных Person father, наивная реализация создаст фрагмент семейного древа этого человека, а не самого Person. Если существуют двусторонние ассоциации, наивная реализация будет повторяться до тех пор, пока вы не получите переполнение стека Так что, возможно, пропустите элементы данных, которые содержат ассоциации?

  8. Но как насчет типа значения Marriage, имеющего Person husband и Person wife членов данных? Об этих ассоциациях следует сообщить Marriage.toString(). Самый простой способ заставить все методы toString() работать - Person.toString() - сообщать только о полях идентификации (Person.name или System.identityhashCode(this)) Person.

  9. Так что кажется, что предоставленная реализация toString() на самом деле не так уж плоха для классов сущностей. В таком случае зачем его отменять?


Чтобы сделать это конкретным, рассмотрите следующий код:

public final class Person {

   public void marry(Person spouse)
   {
      if (spouse == this) {
         throw new IlegalArgumentException(this + " may not marry self");
      }
      // more...
   }

   // more...
}

Насколько полезным будет переопределение toString() при отладке IlegalArgumentException, брошенного Person.marry()?

Ответы [ 5 ]

10 голосов
/ 03 февраля 2011

Так что кажется, что предоставленная реализация toString () на самом деле не так уж плоха для классов сущностей.В таком случае, зачем его отменять?

Что заставляет вас думать, что цель toString() - просто получить уникальную строку?Это не его цель.Его цель - дать вам контекст об экземпляре, и просто имя класса и хэш-код не дают вам контекста.

Edit

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

9 голосов
/ 04 февраля 2011

Пункт № 3 является слабым звеном в этом аргументе, и я на самом деле категорически не согласен с ним.Ваш инвариант (переупорядочен)

x.equals(y) == x.toString().equals(y.toString()); 

Я бы сказал, скорее:

x.equals(y) → x.toString().equals(y.toString()); 

То есть, логическое следствие.Если x и y равны, то их toString () должны быть равны, но равный toString () означает, что не обязательно означает, что объекты равны (представьте себе отношение equals(): hashCode();равные объекты должны иметь одинаковый хеш-код, но тот же хеш-код может * не означать, что объекты равны).

По сути, toString() на самом деле не имеет никакого значения'в программном смысле, и я думаю, что вы пытаетесь наполнить его одним.toString() наиболее полезен в качестве инструмента для ведения журнала и т. Д .;Вы спрашиваете, насколько полезным было бы получить переопределенный toString():

throw new IlegalArgumentException(this + " may not marry self");

Я бы сказал, что это массово полезно.Скажем, вы заметили много ошибок в своих журналах и видите:

IllegalArgumentException: com.foo.Person@1234ABCD cannot marry self
IllegalArgumentException: com.foo.Person@2345BCDE cannot marry self
IllegalArgumentException: com.foo.Person@3456CDEF cannot marry self
IllegalArgumentException: com.foo.Person@4567DEFA cannot marry self

что вы делаете?Вы понятия не имеете что происходит.Если вы видите:

IllegalArgumentException: Person["Fred Smith", id=678] cannot marry self
IllegalArgumentException: Person["Mary Smith", id=679] cannot marry self
IllegalArgumentException: Person["Mustafa Smith", id=680] cannot marry self
IllegalArgumentException: Person["Emily-Anne Smith", id=681] cannot marry self

, то у вас действительно есть шанс выяснить, что происходит («Эй, кто-то пытается заставить семью Смитов жениться на себе»), и это может фактически помочь в отладке и т. Д.Идентификаторы объектов Java не дают никакой информации вообще .

3 голосов
/ 03 февраля 2011

Да, оно того стоит. ToString помогает дать ощутимый визуальный вывод о состоянии объекта. IMO, это особенно важно в сущности, потому что ORM или другие сторонние библиотеки печатают объект как часть своей стратегии ведения журналов довольно часто.

logger.debug("Entity: {}", entity);

явно будет неявно вызывать toString ().

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

Вы бы предпочли это:

DEBUG | pattern: test.entity.MyEntity@12345f

или это:

DEBUG | pattern: MyEntity [id = 1234.0, foo=bar, bar=baz]

Короче говоря, единственная причина, по которой вы не переопределите toString, - это лень. В последних выпусках Eclipse даже есть генератор toString!

3 голосов
/ 03 февраля 2011

Наличие метода toString() в классах сущностей может быть чрезвычайно полезным для целей отладки.С практической точки зрения, использование IDE-шаблонов или что-то вроде аннотации Project Lombok @ToString значительно упрощает эту задачу и позволяет быстро реализовать ее.

2 голосов
/ 03 февраля 2011

Я всегда использую toString () для своих собственных целей, а не из-за каких-то технических требований.Когда у меня есть класс Person, метод toString возвращает имя человека.Не больше, не меньше.Это не уникально, но для целей отладки достаточно посмотреть, что это за человек.Особенно в веб-разработке это очень удобно, когда мне просто нужно написать имя объекта в JSP, чтобы получить имя человека, так что я знаю, что у меня есть правильный объект.как идентификатор базы данных), тогда это идеальный кандидат для toString (), поэтому он может вернуть #294: John Doe.Но уникальность не обязательна.

Действительно ... Даже если мистер Блох так говорит ... Я не думаю, что имеет смысл иметь какие-либо правила для реализации toString ().Это имеет смысл для hashCode () и equals (), но не для toString ().

...