Цикл статей «Project Lombok».
Следующая статья — «Lombok @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor».
Предыдущая статья — «Lombok @ToString».
К любому объявлению класса может быть применена аннотация @EqualsAndHashCode, чтобы lombok сгенерировал методы equals(Object other) и hashCode(). По умолчанию используются все нестатические, не-transient поля, но вы можете исключить некоторые поля, перечислив их в параметре exclude. Или вы можете явно указать, какие поля вы хотите использовать, перечислив их в параметре of.
Если @EqualsAndHashCode применяется к классу, который расширяет другой класс, то эта возможность становится более сложной. Обычно автогенерируемые методы equals и hashCode для этих классов являются плохой идеей, так как родительский класс тоже определяет поля, которые тоже нужно использовать в equals и hashCode, но они НЕ используются. Устанавливая callSuper в true, вы можете включить методы equals и hashCode суперкласса в сгенерированные методы. Для hashCode вставляется вызов super.hashCode() в алгоритм вычисления, а для equals сгенерированный метод возвращает false, если реализация метода в родительском классе решит, что объект не равен переданному. Имейте в виду, что не все реализации equals обрабатывают эту ситуацию правильно. Однако вы можете без опасений вызвать метод родительского класса, если этот метод тоже сгенерирован lombok. Если вы имеете определённый родительский класс, который вам нужно поддерживать, то укажите callSuper, чтобы показать, что вы рассмотрели его, иначе выйдет предупреждение.
Установка callSuper в true, когда вы не хотите расширять что-либо (когда вы наследуетесь от java.lang.Object), вызывает ошибку компиляции, потому что это это меняет поведение сгенерированных equals() и hashCode() на то, что было у этих методов в java.lang.Object, то есть только те же самые объекты будут равны и иметь тот же hashCode. Не установка callSuper в true при расширении другого класса вызовет предупреждение, потому что пока в родительском классе нет полей (важных при сравнении), lombok не может сгенерировать реализацию, которая примет во внимание поля родительского класса. Вам нужно написать свои собственные реализации, либо положиться на возможности callSuper.
Новое в Lombok 0.10: Если ваш класс не final и не наследуется прямо от java.lang.Object, lombok генерирует метод canEqual, который означает, что JPA-прокси всё ещё могут быть равны своему базовому классу, но дочерние классы, добавляющие новые состояния, не ломают соглашение об equals. Если все классы иерархии являются мешаниной из классов scala и lombok, то сравнение будет просто «работать». Если вам нужно написать свои собственные методы equals, то всегда переопределяйте canEqual, если вы поменяли equals и hashCode.
Новое в Lombok 1.14.0: Чтобы добавить аннотации к параметру equals (и если необходимо к canEqual), вы можете использовать onParam=@__({@AnnotationsHere}) . Но будьте осторожны! Это экпериментальная возможность.
С использованием Lombok
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import lombok.EqualsAndHashCode; @EqualsAndHashCode(exclude = { "id", "shape" }) public class EqualsAndHashCodeExample { private transient int transientVar = 10; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; private int id; public String getName() { return this.name; } @EqualsAndHashCode(callSuper = true) public static class Square extends Shape { private final int width, height; public Square(int width, int height) { this.width = width; this.height = height; } } } |
Чистая Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import java.util.Arrays; public class EqualsAndHashCodeExample { private transient int transientVar = 10; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; private int id; public String getName() { return this.name; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof EqualsAndHashCodeExample)) return false; EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o; if (!other.canEqual((Object) this)) return false; if (this.getName() == null ? other.getName() != null : !this.getName() .equals(other.getName())) return false; if (Double.compare(this.score, other.score) != 0) return false; if (!Arrays.deepEquals(this.tags, other.tags)) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; final long temp1 = Double.doubleToLongBits(this.score); result = (result * PRIME) + (this.name == null ? 43 : this.name.hashCode()); result = (result * PRIME) + (int) (temp1 ^ (temp1 >>> 32)); result = (result * PRIME) + Arrays.deepHashCode(this.tags); return result; } protected boolean canEqual(Object other) { return other instanceof EqualsAndHashCodeExample; } public static class Square extends Shape { private final int width, height; public Square(int width, int height) { this.width = width; this.height = height; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Square)) return false; Square other = (Square) o; if (!other.canEqual((Object) this)) return false; if (!super.equals(o)) return false; if (this.width != other.width) return false; if (this.height != other.height) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; result = (result * PRIME) + super.hashCode(); result = (result * PRIME) + this.width; result = (result * PRIME) + this.height; return result; } protected boolean canEqual(Object other) { return other instanceof Square; } } } |
Поддерживаемые ключи конфигурации
1 |
lombok.equalsAndHashCode.doNotUseGetters = [true | false] (default: false) |
Если установлен в true, то lombok будет обращаться к полям напрямую вместо использования методов получения значений (если есть) при генерации методов equals и hashCode. Параметр аннотации doNotUseGetters, если явно указан, имеет приоритет перед этой настройкой.
1 |
lombok.equalsAndHashCode.flagUsage = [warning | error] (default: not set) |
Lombok будет помечать любое использование @EqualsAndHashCode предупреждением или ошибкой, если настроено.
Примечания
Массивы могут быть сравнены/вычислена хеш-функция в глубину, что означает, что массивы, содержащие сами себя генерируют исключение StackOverflowError. Однако это поведение не отличается от ArrayList.
Вы можете спокойно полагать, что реализация hashCode не изменится в следующих версиях lombok, однако гарантия не выдолблена на камне. Если будет значительное улучшение производительности при использовании другого алгоритма вычисления хеша, то это будет изменено в будущей версии.
При сравнении значения NaN у типов float и double рассматриваются как равные друг другу, даже не смотря на то что NaN==NaN возвращает false. То же самое с методом equals в java.lang.Double. Этот необходимо для того чтобы обеспечить равенство при сравнении объекта с точной копией самого себя.
Если уже есть метод с именем hashCode или equals (независимо от типа возвращаемого значения), то методы не генерируются, и возникает предупреждение. Эти два метода должны быть синхронизированы друг с другом, что lombok не может гарантировать до тех пор, пока он не генерирует все эти методы, поэтому вы всегда будете получать предупреждение, если один или оба метода уже существуют. Вы можете пометить любой метод @lombok.experimental.Tolerate, чтобы скрыть их от lombok.
Попытка исключения полей, которые не существуют или уже исключены (потому что они статические или transient), приводит к предупреждениям на этих полях. По этой причине вам не нужно беспокоиться об опечатках.
Наличие одновременно exclude и of генерирует предупреждение. Параметр exclude в этом случае игнорируется.
По умолчанию все переменные, начинающиеся с символа «$», исключаются автоматически. Вы можете их добавить только с помощью параметра of.
Если есть метод получения значения для включаемого поля, то вызывается он вместо прямого доступа к полю. Это поведение может быть изменено: @EqualsAndHashCode(doNotUseGetters = true)
Цикл статей «Project Lombok».
Следующая статья — «Lombok @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor».
Предыдущая статья — «Lombok @ToString».