Мне нравится этот вопрос! Главным образом потому, что на него почти никогда не отвечали или отвечали плохо. Как будто никто еще не понял это. Девственная территория:)
Во-первых, даже не думайте об использовании equals
. Контракт equals
, как определено в javadoc, является отношением эквивалентности (рефлексивным, симметричным и переходным), не отношением равенства. Для этого он также должен быть антисимметричным. Единственная реализация equals
, которая является (или может когда-либо существовать) отношением истинного равенства, это реализация в java.lang.Object
. Даже если вы использовали equals
для сравнения всего на графике, риск разрыва контракта довольно высок. Как отметил Джош Блох в Effective Java , контракт равных очень легко разорвать:
«Нет никакого способа расширить инстанцируемый класс и добавить аспект, сохраняя контракт равных»
Кроме того, что хорошего в любом случае делает булев метод? Было бы неплохо на самом деле заключить все различия между оригиналом и клоном, не так ли? Кроме того, я предполагаю, что вы не хотите беспокоиться о написании / поддержании кода сравнения для каждого объекта в графике, а скорее о том, что вы будете масштабировать с исходным кодом, который изменяется со временем.
Таааак, вам действительно нужен инструмент сравнения состояний. То, как этот инструмент реализован, действительно зависит от характера вашей доменной модели и ваших ограничений производительности. По моему опыту, нет общей магической пули. И будет медленным в течение большого количества итераций. Но для проверки полноты операции клонирования, она отлично справится с этой задачей. Два ваших лучших варианта - сериализация и рефлексия.
Некоторые проблемы, с которыми вы столкнетесь:
- Порядок сбора: следует ли считать две коллекции одинаковыми, если они содержат одинаковые объекты, но в другом порядке?
- Какие поля игнорировать: Transient? Статический
- Эквивалентность типа. Должны ли значения полей иметь одинаковый тип? Или это нормально для одного, чтобы расширить другой?
- Есть еще, но я забываю ...
XStream довольно быстр и в сочетании с XMLUnit сделает работу всего за несколько строк кода. XMLUnit хорош тем, что может сообщать обо всех различиях или просто останавливаться на первом обнаруженном. И его вывод включает в себя xpath к различным узлам, что приятно. По умолчанию он не допускает неупорядоченные коллекции, но его можно настроить для этого. Внедрение специального обработчика различий (называемого DifferenceListener
) позволяет указать способ обработки различий, включая игнорирование порядка. Однако, как только вы захотите сделать что-то помимо простой настройки, вам будет сложно писать, и детали будут привязаны к конкретному объекту домена.
Мое личное предпочтение - использовать рефлексию для циклического прохождения всех объявленных полей и углубления в каждое из них, отслеживая различия по мере продвижения Слово предупреждения: не используйте рекурсию, если вы не любите исключения переполнения стека. Держите вещи в области с помощью стека (используйте LinkedList
или что-то). Я обычно игнорирую переходные и статические поля и пропускаю пары объектов, которые я уже сравнил, поэтому я не зацикливаюсь на бесконечных циклах, если кто-то решил написать самоссылочный код (однако я всегда сравниваю примитивные оболочки независимо от того, что , поскольку ссылки на одни и те же объекты часто используются повторно). Вы можете настроить все заранее, чтобы игнорировать упорядочение коллекции и игнорировать специальные типы или поля, но мне нравится определять свои политики сравнения состояний для самих полей с помощью аннотаций. Это, IMHO, как раз то, для чего предназначались аннотации, чтобы сделать метаданные о классе доступными во время выполнения. Что-то вроде:
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
Я думаю, что это действительно сложная проблема, но полностью решаемая! И если у вас есть что-то, что работает для вас, это действительно очень удобно :) 1043 *
Итак, удачи. И если вы придумаете что-то, что является просто гением, не забудьте поделиться!