В качестве лучшей практики в Java классы POJO или объекты Data всегда объявляют поля закрытыми и предоставляют доступ к ним через методы получения. В редких случаях мы можем столкнуться с ситуациями, когда поля-члены являются закрытыми, а класс не предоставляет публичные методы доступа. Например, класс мог быть сгенерирован из инструмента.
В таких случаях, когда у нас нет доступа к исходному коду и мы не можем добавить метод equals() в файл класса Java, мы можем воспользоваться классом EqualsBuilder, чтобы создать хороший метод equals для любого класса за пределами этого класса.
1. Метод EqualsBuilder.reflectionEquals()
ReflectionEquals(), как следует из названия, использует рефлексию(метод AccessibleObject.setAccessible) для изменения видимости закрытых полей в сгенерированном классе. Затем он перебирает все поля(если они явно не исключены из сравнения) и сравнивает их значения.
- Примитивы сравниваются по их ценности.
- Непримитивные элементы сравниваются с помощью метода equals().
- Переходные члены не проверяются, поскольку они, скорее всего, являются производными полями и не являются частью значения объекта.
- Статические поля не тестируются.
- Поля суперкласса также проверяются на равенство.
Мы можем добавить библиотеку Apache Common Lang, включив зависимость « org.apache.commons:commons-lang3 ».
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.17.0</version></dependency>
Типичный вызов метода reflectEquals() будет выглядеть так:
CustomObject firstObject = ...;CustomObject secondObject = ...;boolean isEqual = EqualsBuilder.reflectionEquals(firstObject, secondObject);
Чтобы исключить определенные поля из сравнения, мы можем передать список имен полей в качестве третьего параметра метода.
List<String> excludeFields = List.of("fieldname1", "fieldname2");boolean isEqual = EqualsBuilder.reflectionEquals(firstObject, secondObject, excludeFields);
2. Демонстрация
Предположим, у нас есть класс Java Person, который был сгенерирован с использованием сторонней библиотеки и не содержит открытого метода получения.
class Person {private Long id;private String name;private String email;private String phone;public Person(Long id, String name, String email, String phone) {this.id = id;this.name = name;this.email = email;this.phone = phone;}// Getter methods NOT available}
Мы можем сравнить два экземпляра класса Person на предмет равенства следующим образом:
Person p1 = new Person(1L, "TestName1", "TestEmail1", "TestPhone1");Person p2 = new Person(1L, "TestName1", "TestEmail1", "TestPhone1");boolean result = EqualsBuilder.reflectionEquals(p1, p2);System.out.println("p1 equals p2 :: " + result); // p1 equals p2 :: true
3. Исключение полей из сравнения на равенство
Предположим, мы не хотим сравнивать поле «id» во время сравнения equals и хотим сравнить только поля: имя, email и телефон. В таком случае мы можем передать имя поля «id» как список в аргументе метода reflectEquals().
Person p1 = new Person(1L, "TestName1", "TestEmail1", "TestPhone1");Person p2 = new Person(2L, "TestName1", "TestEmail1", "TestPhone1");//boolean result = EqualsBuilder.reflectionEquals(p1, p2); // False because id is differentList<String> excludeFields = List.of("id");boolean result = EqualsBuilder.reflectionEquals(p1, p2, excludeFields);System.out.println("p1 equals p2 :: " + result); // p1 equals p2 :: true
4. Оформление наследства
Что делать, если объект-значение, который мы сравниваем, является частью вложенной иерархии классов, и мы не хотим сравнивать поля в суперклассе по какой-либо причине. Это также возможно, если задать аргумент метода reflectUpToClass.
Возьмем пример следующей иерархии:
class BaseVo implements Serializable {private Long id;public BaseVo(Long id) {this.id = id;}}class ChildVo extends BaseVo implements Serializable {private String name;private String email;private String phone;public ChildVo(Long id, String name, String email, String phone) {super(id);this.name = name;this.email = email;this.phone = phone;}}
При сравнении объектов на равенство мы хотим сравнивать только поля в ChildVo. Поля в BaseVo сравнивать не следует.
В следующем примере поле id принадлежит BaseVo, и его значение не будет использоваться в сравнении equals().
ChildVo child1 = new ChildVo(1L, "TestName1", "TestEmail1", "TestPhone1");ChildVo child2 = new ChildVo(2L, "TestName1", "TestEmail1", "TestPhone1");boolean result = new EqualsBuilder().setReflectUpToClass(ChildVo.class).reflectionAppend(child1, child2).isEquals();System.out.println(result); // true
5. Резюме
Как обсуждалось в Start, это очень маловероятно, чтобы столкнуться с ситуациями, когда класс имеет только закрытые поля и не имеет открытого метода доступа. Но когда мы сталкиваемся с такой ситуацией и хотим выполнить проверку равенства, то EqualsBuilder.setReflectUpToClass является отличным выбором.
Ссылка: Документация по Java для класса EqualsBuilder