Как вы сравниваете объекты одного класса с разными наборами полей? — java
Pavel_Volkov (Pavel Volkov)
#1
Привет.
Есть класс User с полями name, lastname, username, password, role. В нем сгенерированы hashcode() и equals().
Я ищу алгоритм, дающий возможность в каждой проверке равенства двух User самостоятельно определять какие поля надо сравнивать, а какие игнорировать.
1 тест: взять всех пользователей со страницы админки и сравнить их с данными из бд.
На странице у нас есть инфа только по username и role.
Из базы мы получим все поля описанные в классе.
Если сделать Hashset usersFromUi equals Hashset usersFromBd, то проверка зафейлится, т.к. из бд мы получим модель полнее.
Для сравнения можно:
а) заНуллить ненужные поля в объектах из бд в цикле после их получения.
или
б) Дополнить пустые поля объектов из UI полями из БД (select по username)
или
в) сделать класс-компаратор, в котором продублировать имена полей класса User, но с типами bool.
Перед сравнением создавать объект этого класса и поля, которые хочешь сравнивать инициализировать как true, потом сделать метод, который будет принимать две коллекции и компаратор и пропускать при сравнении все false поля.
г) какая-нибудь магия
MOSTOR
#2
А почему бы из базы не брать только username и role ?
Navar4eg (Alexandr Navara)
#3
Вариант если используете assertj: унаследоваться от org. assertj.core.api.Assertions, определить внутри метод isEqualTo(final User anotherUser). Если не используете — я бы выбрал что-то похожее на вариант “в” — класс, который знает что сравнивать и имеет статический метод для сравнения двух объектов одного класса. Варианты “а” и “б” — точно отбросил бы так как поля могут быть и read-only, и вообще — мутировать объект без необходимости как-то не хочется.
Еще вариант — создавать класс-холдер для юзера, инициализировать его данными с юайки, и внутри сделать метод типа “verifyInBase”, который через DAO вытащит нужные данные из базы и выполнит проверку. Такой вариант, имхо, лучше, так как может потребоваться проверка не только таблицы Users, а и чего-нибудь еще.
А вообще — почему используется один и тот же класс для юзера с юайки и для юзера из базы? По сути, Вам нужно сравнить сущности разных классов — скажем, UserView и UserDto. Но в любом случае, на решение это не влияет.
ArtOfLife (Sergey Korol)
#4
д) переопределить equals
и hashCode
таким образом, чтобы обрабатывались только необходимые поля.
3 симпатии
vmaximv (vmaximv)
#5
е)
Ваш тест не уменьшает энтропию AUT и следовательно не является тестом. Напишите нормальный тест-кейс и все вопросы уйдут.
1 симпатия
VatslauX (Vatslau)
#6
Не понял если честно вашего бизнесс кейса…
У меня был такой кейс банальный
- Есть объект юзер в базе
- На уай — проверяю чтобы строки правильно отображались в тч. фото аватарки (имя файла)
- Редактирую профайл — снова проверяю чтобы всё было правильно
Ну и 2-й кейс регистрация юзера
В любом случае это были сравнения полей на иквалс…
Dzmitry_Ihnatsyeu (Dzmitry Ihnatsyeu)
#7
На вашем месте я бы не стал определять метод equals для объекта POJO, так это нарушит инкапсуляцию. Если в тесте вы получаете только username and role, сделайте отдельный метод для проверки данных в тесте. Так вы не загоните себя в рамки ограничения теста при дальнейшей разработке. Не ищите сложных решений, очень часто самое простое и есть верное :).
8. Основы Kotlin. Простые классы — Сторінка 2 з 3
Зміст
- Сравнение объектов класса на равенство
- Включение классов
- Переопределение equals для класса
Сравнение объектов класса на равенство
Как нам уже известно, на равенство ==
можно сравнивать не только числа, но и переменные более сложных типов, например, строки или списки. Часто необходимо уметь сравнить на равенство переменные с типом, определённым пользователем в виде lesson8.task2
про шахматную доску и фигуры имеется класс Square
, описывающий одну клетку шахматной доски. В наиболее простой форме он выглядел бы так:
class Square(val column: Int, val row: Int)
Проверим, что будет, если сравнить две одинаковых клетки first
и second
на равенство:
fun main(args: Array<String>) { val first = Square(3, 6) val second = Square(3, 6) println(first == second) }
Если запустить эту главную функцию, мы увидим на консоли результат false
. Почему?
Всё дело в способе работы, принятом в JVM для любых объектов. Каждый раз, когда мы вызываем конструктор какого-либо класса, в динамической памяти JVM создаётся объект этого класса. Ссылка на него запоминается в стеке JVM (подробности будут в разделе 4.
Немного изменим определение класса Square
, добавив впереди модификатор data
. Такое определение обычно читается как “класс с данными”.
data class Square(val column: Int, val row: Int)
Запустив главную функцию ещё раз, мы увидим результат
. При наличии модификатора data
, для объектов класса работает другой способ сравнения на равенство: все свойства первого объекта сравниваются с соответствующими свойствами второго. Поскольку для обоих объектов column = 3
и row = 6
, данные объекты равны.
Помимо этой возможности, классы с данными позволяют представить объект в виде строки, например:
fun main(args: Array<String>) { val first = Square(3, 6) println(first) }
Эта функция выведет на консоль Square(x=3, y=6)
. Попробуйте теперь убрать модификатор data
в определении класса и посмотрите, как изменится вывод.
Каким же образом осуществляется переопределение способа сравнения объектов и способа их представления в виде строки? Для этой цели в Java придуманы две специальные функции. Первая из них называется equals
, она имеет объект-получатель, принимает ещё один объект как параметр и выдаёт результат true
, если эти два объекта равны. Чуть ниже приведён пример переопределения equals
для класса Segment
.
Вторая функция называется toString
. Она также имеет объект-получатель, но не имеет параметров. Её результат — это строковое представление объекта. Например:
class Square(val column: Int, val row: Int) { override fun toString() = "$row - $column" }
Запустив главную функцию выше, мы увидим на консоли строку 6 - 3
. Обратите внимание на модификатор override
перед определением toString()
. Он указывает на тот факт, что данная функция переопределяет строковое представление по умолчанию. Подробнее об этом опять-таки в разделе 9.
О других возможностях классов с данными можно прочитать здесь: https://kotlinlang.org/docs/reference/data-classes.html.
Включение классов
Система классов была бы очень неполноценной, если бы нам приходилось использовать классы сами по себе, в отрыве друг от друга. Поэтому у классов есть множество способов взаимодействовать друг с другом. Самый простой из них — включение объекта одного класса внутрь другого класса. Например:
data class Triangle(val a: Point, val b: Point, val c: Point) { // ... } data class Segment(val begin: Point, val end: Point) { // ... }
Здесь треугольник (Triangle) имеет три свойства a
, b
и c
, каждое из которых, в свою очередь, имеет тип Point
— точка. В таких случаях говорят, что треугольник включает три точки, состоит из трёх точек или описывается тремя точками. Отрезок (Segment) имеет два таких же свойства begin
и end
— то есть описывается своим началом и концом.
Точки, в свою очередь, описываются двумя вещественными координатами. Например:
fun main(args: Array<String>) { val t = Triangle(Point(0.0, 0.0), Point(3.0, 0.0), Point(0.0, 4.0)) println(t.b.x) // 3.0 }
При вызове println
мы прочитали свойство x
свойстваb
треугольника t
. Для этого мы дважды использовали точку для обращения к свойству объекта.
Переопределение equals для класса
Рассмотрим пример переопределения equals
для класса Segment
. Дело в том, что для отрезка, вообще говоря, всё равно, в каком порядке в нём идут начало и конец, то есть отрезок AB равен отрезку BA. Применение способа сравнения на равенство, действующего для классов с данными по умолчанию, даст нам другой результат: AB не равно BA.
data class Segment(val begin: Point, val end: Point) { override fun equals(other: Any?) = other is Segment && ((begin == other. begin && end == other.end) || (begin == other.end && end == other.begin)) }
Модификатор override
перед определением equals
говорит о том, что мы хотим изменить уже имеющийся метод сравнения на равенство. Единственный параметр other
данного метода обязан иметь тип Any?
, то есть “любой, в том числе null”. В Котлине действует правило: абсолютно любой тип является разновидностью Any?
, то есть значение любой переменной или константы можно использовать как значение типа Any?
. Это обеспечивает возможность сравнения на равенство чего угодно с чем угодно.
Результат equals
имеет тип Boolean
. В первую очередь, мы должны проверить, что переданный нам аргумент — тоже отрезок: other is Segment
. Ключевое слово is в Котлине служит для определения принадлежности значения к заданному типу. Аналогично !is делает проверку на не принадлежность.
Если аргумент — отрезок, мы сравниваем точки двух имеющихся отрезков на равенство, с точностью до их перестановки. Если же аргумент — не отрезок, то логическое И в любом случае даст результат false. Обратите внимание, что справа от &&
мы вправе использовать other
как отрезок (например, используя его begin
и end
), поскольку проверка этого факта была уже сделана.
Сторінки: 1 2 3
Есть ли утилита Java для глубокого сравнения двух объектов?
Мне нравится этот вопрос! В основном потому, что на него почти никогда не отвечают или отвечают плохо. Как будто еще никто не разобрался. Девственная территория 🙂
Во-первых, даже не думайте, что об использовании равняется
. Контракт равен
, как определено в javadoc, является отношением эквивалентности (рефлексивным, симметричным и транзитивным), , а не отношением равенства. Для этого он также должен быть антисимметричным. Единственная реализация равно
, то есть (или когда-либо могло быть) истинным отношением равенства является отношение java.lang.Object
. Даже если вы использовали равно
для сравнения всего на графике, риск нарушения контракта довольно высок. Как указал Джош Блох в Effective Java , контракт равенства очень легко нарушить:
«Просто нет способа расширить инстанцируемый класс и добавить аспект, сохраняя контракт равенства»
Кроме того, что хорошего логический метод действительно вам так или иначе? Было бы неплохо отразить все различия между оригиналом и клоном, не так ли? Кроме того, я предполагаю, что вы не хотите беспокоиться о написании/сопровождении кода сравнения для каждого объекта на графике, а скорее ищете что-то, что будет масштабироваться вместе с исходным кодом по мере его изменения с течением времени.
Таааак, вам действительно нужен какой-нибудь инструмент для сравнения состояний. То, как этот инструмент реализован, действительно зависит от характера вашей модели предметной области и ваших ограничений производительности. По моему опыту, универсального волшебного средства не существует. И это будет медленным на большом количестве итераций. Но для проверки полноты операции клонирования он вполне подойдет. Двумя лучшими вариантами являются сериализация и отражение.
Некоторые проблемы, с которыми вы столкнетесь:
- Порядок коллекций: следует ли считать две коллекции похожими, если они содержат одни и те же объекты, но в разном порядке?
- Какие поля игнорировать: Transient? Статический?
- Эквивалентность типов: Должны ли значения полей быть одного и того же типа? Или можно, чтобы одно расширяло другое?
- Есть еще, но я забыл…
XStream работает довольно быстро и в сочетании с XMLUnit сделает эту работу всего за несколько строк кода. XMLUnit удобен тем, что может сообщать обо всех различиях или просто останавливаться на первом найденном. И его вывод включает xpath к различным узлам, что приятно. По умолчанию он не разрешает неупорядоченные коллекции, но его можно настроить для этого. Внедрение специального обработчика различий (называемого DifferenceListener
) позволяет указать способ обработки различий, включая игнорирование порядка. Однако, как только вы захотите сделать что-то помимо простейшей настройки, писать становится сложно, а детали, как правило, привязаны к конкретному предметному объекту.
Лично я предпочитаю использовать отражение для циклического просмотра всех объявленных полей и детализации каждого из них, отслеживая различия по мере продвижения. Слово предупреждения: не используйте рекурсию, если вам не нравятся исключения переполнения стека. Держите вещи в пределах стека (используйте LinkedList
или что-то в этом роде). Обычно я игнорирую переходные и статические поля и пропускаю пары объектов, которые я уже сравнивал, чтобы не попасть в бесконечные циклы, если кто-то решит написать самореферентный код (однако я всегда сравниваю примитивные оболочки независимо от того, что происходит). , так как одни и те же ссылки на объекты часто используются повторно).