Из-за того, как я склонен проектировать сложные объекты, у меня здесь очень простое решение.
При проектировании сложного объекта, для которого мне нужно написать метод equals (и, следовательно, метод hashCode), я стремлюсь написать средство визуализации строки и использовать методы equals класса String и методы hashCode.
Рендерер, конечно, не toString: людям не обязательно должно быть легко читать, и он включает в себя все и только значения, которые мне нужно сравнивать, и по привычке я помещаю их в порядок, который контролирует способ, которым я хотел бы, чтобы они сортировали; ничего из этого не обязательно верно для метода toString.
Естественно, я кеширую эту визуализированную строку (а также значение hashCode). Обычно он закрытый, но оставление кэшированной строки package-private позволит вам увидеть это из ваших модульных тестов.
Между прочим, это не всегда то, с чем я сталкиваюсь в поставляемых системах, конечно - если тестирование производительности показывает, что этот метод слишком медленный, я готов заменить его, но это редкий случай. Пока что это произошло только один раз, в системе, в которой изменчивые объекты быстро менялись и часто сравнивались.
Причина, по которой я это делаю, заключается в том, что написание хорошего хэш-кода не является тривиальным и требует тестирования (*), тогда как использование в String позволяет избежать тестирования.
(* Учтите, что шаг 3 в рецепте Джоша Блоха по написанию хорошего метода hashCode состоит в том, чтобы протестировать его, чтобы убедиться, что «равные» объекты имеют равные значения hashCode, и что вы охватили все возможные варианты. сам по себе не тривиален. Более тонким и даже более сложным для проверки является распределение)