Как вы сравниваете объекты одного класса с разными наборами полей? — 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)

30.Август.2017 06:19:59

#5

е)

Ваш тест не уменьшает энтропию AUT и следовательно не является тестом. Напишите нормальный тест-кейс и все вопросы уйдут.

1 симпатия

VatslauX (Vatslau)

#6

Не понял если честно вашего бизнесс кейса…
У меня был такой кейс банальный

  1. Есть объект юзер в базе
  2. На уай — проверяю чтобы строки правильно отображались в тч. фото аватарки (имя файла)
  3. Редактирую профайл — снова проверяю чтобы всё было правильно

Ну и 2-й кейс регистрация юзера

В любом случае это были сравнения полей на иквалс…

Dzmitry_Ihnatsyeu (Dzmitry Ihnatsyeu)

#7

На вашем месте я бы не стал определять метод equals для объекта POJO, так это нарушит инкапсуляцию. Если в тесте вы получаете только username and role, сделайте отдельный метод для проверки данных в тесте. Так вы не загоните себя в рамки ограничения теста при дальнейшей разработке. Не ищите сложных решений, очень часто самое простое и есть верное :).

8. Основы Kotlin. Простые классы — Сторінка 2 з 3

Зміст

  1. Сравнение объектов класса на равенство
  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.

5). По умолчанию, при сравнении объектов на равенство сравниваются друг с другом ссылки, а не содержимое объектов.

Немного изменим определение класса Square, добавив впереди модификатор data. Такое определение обычно читается как “класс с данными”.

data class Square(val column: Int, val row: Int)

Запустив главную функцию ещё раз, мы увидим результат 

true. При наличии модификатора 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) имеет три свойства ab и 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 или что-то в этом роде). Обычно я игнорирую переходные и статические поля и пропускаю пары объектов, которые я уже сравнивал, чтобы не попасть в бесконечные циклы, если кто-то решит написать самореферентный код (однако я всегда сравниваю примитивные оболочки независимо от того, что происходит). , так как одни и те же ссылки на объекты часто используются повторно).