Ответы на вопросы на собеседование Java core (часть 2).
- Возможно ли при переопределении (override) метода изменить:
- Модификатор доступа
- Возвращаемый тип
- Тип аргумента или количество
- Имя аргументов
- Изменять порядок, количество или вовсе убрать секцию throws?
- Да, если расширять (package -> protected -> public)
- Да, если выполняется Downcasting(понижающее преобразование, преобразование вниз по иерархии) то есть возвращаемый тип в переопределенном методе класса наследника должен быть НЕ шире чем в классе родителе (Object -> Number -> Integer)
- Нет, в таком случае происходит Overload(перегрузка)
- Да
- Возможно изменять порядок. Возможно вовсе убрать секцию throws в методе, так как она уже определена. Так же возможно добавлять новые исключения, которые наследуются от объявленных или исключения времени выполнения.
Переопределение методов действует при наследовании классов, т.е. в классе наследнике объявлен метод с такой же сигнатурой что и в классе родителе. Значит этот метод переопределил метод своего суперкласса.
Несколько нюансов по этому поводу:
- Модификатор доступа в методе класса наследника должен быть НЕ уже чем в классе родителе, иначе будет ошибка компиляции.
- Описание исключения в переопределенном методе класса наследника должен быть НЕ шире чем в классе родителе, иначе ошибка компиляции.
- Метод обьявленный как «private» в классе родителе нельзя переопределить!
- Что такое autoboxing?
Autoboxing/Unboxing — автоматическое преобразование между скалярными типами Java и соответствующими типами-врапперами (например, между int — Integer). Наличие такой возможности сокращает код, поскольку исключает необходимость выполнения явных преобразований типов в очевидных случаях.
- Что такое Generics?
«Java Generics» — это технический термин, обозначающий набор свойств языка позволяющих определять и использовать обобщенные типы и методы. Обобщенные типы или методы отличаются от обычных тем, что имеют типизированные параметры.
Примером дженериков или обобщенных типов может служить библитека с коллекциями в Java. Например, класс LinkedList<E> — типичный обобщенный тип. Он содержит параметр E, который представляет тип элементов, которые будут храниться в коллекции. Вместо того, чтобы просто использовать LinkedList, ничего не говоря о типе элемента в списке, мы можем использовать LinkedList<String> или LinkedList<Integer>. Создание объектов обобщенных типов происходит посредством замены параметризированных типов реальными типами данных. Класс типа LinkedList<E> — обобщенный тип, который содержит параметр E. Создание объектов, типа LinkedList<String> или LinkedList<Integer> называются параметризированными типами, а String и Integer — реальные типы аргументов.
- Какова истинная цель использования обобщенных типов в Java?
Обобщенные типы в Java были изобретены, в первую очередь, для реализации обобщенных коллекций.
- Каким образом передаются переменные в методы, по значению или по ссылке?
В java параметры в методы передаются по значению, тоесть создаются копии параметров и с ними ведется работа в методе. В случае с примитивными типами, то при передачи параметра сама переменная не будет меняться так как в метод просто копируется ее значение.
А вот при передачи объекта копируется ссылка на объект, тоесть если в методе мы поменяем состояние объекта, то и за методом состояние объекта тоже поменяется. Но если мы этой копии ссылки попытаемся присвоить новую ссылку на обьект, то старая ссылка у нас не изменится.
В случае передачи по значению параметр копируется. Изменение параметра не будет заметно на вызывающей стороне.
В Java объекты всегда передаются по ссылке, а примитивы — по значениюю.
- Какие методы есть у класса Object?
Object это базовый класс для всех остальных объектов в Java. Каждый класс наследуется от Object.
Методы класса Object:
- public final native Class getClass()
- public native int hashCode()
- public boolean equals(Object obj)
- protected native Object clone() throws CloneNotSupportedException
- public String toString()
- public final native void notify()
- public final native void notifyAll()
- public final native void wait(long timeout) throws InterruptedException
- public final void wait(long timeout, int nanos) throws InterruptedException
- public final void wait() throws InterruptedException
- protected void finalize() throws Throwable
- Правила переопределения метода Object.equals().
- Используйте оператор == что бы проверить ссылку на объект, переданную в метод equals. Если ссылки совпадают — вернуть true. Это не обязательно, нужно скорее для оптимизации, но может сэкономить время в случае «тяжёлых» сравнений.
- Используйте оператор instanceof для проверки типа аргумента. Если типы не совпадают, вернуть false.
- Преобразуйте аргумент к корректному типу. Так как на предыдущем шаге мы выполнили проверку, преобразование корректно.
- Пройтись по всем значимым полям объектов и сравнить их друг с другом. Если все поля равны — вернуть true. Для сравнения простых типов использовать ==. Для полей со ссылкой на объекты использовать equals. float преобразовывать в int с помощью Float.floatToIntBits и сравнить с помощью ==. double преобразовывать в long с помощью Double.doubleToLongBits и сравнить с помощью ==. Для коллекций вышеперечисленные правила применяются к каждому элементу коллекции. Нужно учитывать возможность null полей/объектов. Очерёдность сравнения полей может существенно влиять на производительность.
- Закончив реализацию equals задайте себе вопрос, является ли метод симметричным, транзитивным и непротиворечивым.
И ещё несколько дополнительных правил.
- Переопределив equals, всегда переопределять hashCode.
- Не использовать сложную семантику в equals (типа определения синонимов). equals должен сравнивать поля объектов, не более.
- Если вы хотите переопределить equals(), какие условия должны удовлетворяться для переопределенного метода?
Метод equals() обозначает отношение эквивалентности объектов. Эквивалентным называется отношение, которое является симметричным, транзитивным и рефлексивным.
- Рефлексивность: для любого ненулевого x, x.equals(x) вернет true;
- Транзитивность: для любого ненулевого x, y и z, если x.equals(y) и y.eqals(z) вернет true, тогда и x.equals(z) вернет true;
- Симметричность: для любого ненулевого x и y, x.equals(y) должно вернуть true, тогда и только тогда, когда y.equals(x) вернет true.
Также для любого ненулевого x, x.equals(null) должно вернуть false.
- Какая связь между hashCode и equals?
Объекты равны, когда a. equals(b)=true и a.hashCode==b.hashcode ->true Но необязательно, чтобы два различных объекта возвращали различные хэш коды(такая ситуация называется коллизией).
- Каким образом реализованы методы hashCode и equals в классе Object?
Реализация же метода hashCode класса Object сделана нативной, т.е. определенной не с помощью Java-кода:
Он обычно возвращает адрес объекта в памяти.
- Что будет, если переопределить equals не переопределяя hashCode? Какие могут возникнуть проблемы?
Они будут неправильно хранится в контейнерах, использующих хэш коды, таких как HashMap, HashSet.
Например HashSet хранит элементы в случайном (на первый взгляд) порядке. Дело в том, что для быстрого поиска HashSet расчитывает для каждлого элемента hashCode и именно по этому ключу ищет и упорядочивает элементы внутри себя
- Есть ли какие-либо рекомендации о том, какие поля следует использовать при подсчете hashCode?
Есть. Необходимо использовать уникальные, лучше примитивные поля, такие как id, uuid, например. Причем, если эти поля задействованы при вычислении hashCode, то нужно их задействовать при выполнении equals.
Общий совет: выбирать поля, которые с большой долью вероятности будут различаться.
- Для чего нужен метод hashCode()?
Существуют коллекции(HashMap, HashSet), которые используют хэш код, как основу при работе с объектами. А если хэш для равных объектов будет разным, то в HashMap будут два равных значения, что является ошибкой. Поэтому необходимо соответствующим образом переопределить метод hashCode().
Х Хеширование — преобразование входного массива данных произвольной длины в выходную битовую строку фиксированной длины. Такие преобразования также называются хеш-функциями или функциями свёртки, а их результаты называют хешем или хеш-кодом.
Хе Хеш-таблице — это структура данных, реализующая интерфейс ассоциативного массива, а именно, она позволяет хранить пары (ключ, значение) и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу.
Выполнение операции в хеш-таблице начинается с вычисления хеш-функции от ключа. Получающееся хеш-значение i = hash(key) играет роль индекса в массиве H. Затем выполняемая операция (добавление, удаление или поиск) перенаправляется объекту, который хранится в соответствующей ячейке массива H[i].
Одним из методов построения хеш-функции есть метод деления с остатком (division method) состоит в том, что ключу k ставится в соответствие остаток от деления k на m, где m — число возможных хеш-значений.
- Правила переопределения метода Object.hashCode().
При реализации hashCode используется несколько простых правил. Прежде всего, при вычислении хеш-кода следует использовать те же поля, которые сравниваются в equals. Это, во-первых, даст равенство хеш-кодов для равных обектов, во-вторых, распределено полученное значение будет точно так же, как и исходные данные. Теоретически, можно сделать так, чтобы хеш-код всегда был равен 0, и это будет абсолютно легальная реализация. Другое дело, что ее ценность будет равна тому же самому нулю.
Далее. Несмотря на то, что хеш-коды равных объектов должны быть равны, обратное неверно! Два неравных объекта могут иметь равные хеш-коды. Решающее значение имеет не уникальность, а скорость вычисления, потому как это приходится делать очень часто. Потому, в некоторых случаях имеет смысл посчитать хеш-код заранее и просто выдавать его по запросу. Прежде всего это стоит делать тогда, когда вычисление трудоемко, а объект неизменен.
- Расскажите про клонирование объектов. В чем отличие между поверхностным и глубоким клонированием?
Чтобы объект можно было клонировать, он должен реализовать интерфейс Cloneable(маркер). Использование этого интерфейса влияет на поведение метода «clone» класс Object. Таким образом
myObj.clone() создаст нам клон нашего объекта, но этот клон будет поверхностный.
Что значит поверхностным? Это значит что клонируется только примитивные поля класса, ссылочные поля не клонируются!
Для того, чтоб произвести глубокое клонирование, необходимо в клонируемом классе переопределить метод clone() и в нем произвести клонирование изменяемых полей объекта.
- Правила переопределения метода Object.clone().
Метод clone() в Java используется для клонирования объектов. Т.к. Java работает с объектами с помощью ссылок, то простым присваиванием тут не обойдешься, ибо в таком случае копируется лишь адрес, и мы получим две ссылки на один и тот же объект, а это не то, что нам нужно. Механизм копирования обеспечивает метод clone() класса Object.
clone() действует как конструктор копирования. Обычно он вызывает метод clone() суперкласса и т.д. пока не дойдет до Object.
Метод clone() класса Object создает и возвращает копию объекта с такими же значениями полей. Object.clone() кидает исключение CloneNotSupportedException если вы пытаетесь клонировать объект не реализующий интерфейс Cloneable. Реализация по умолчанию метода Object.clone() выполняет неполное/поверхностное (shallow) копирование. Если вам нужно полное/глубокое (deep) копирование класса то в методе clone() этого класса, после получения клона суперкласса, необходимо скопировать нужные поля.
Синтаксис вызова clone() следующий:или чаще:
Один из недостатков метода clone(), это тот факт, что возвращается тип Object, поэтому требуется нисходящее преобразование типа. Однако начиная с версии Java 1.5 при переопределении метода вы можете сузить возвращаемый тип.
Пару слов о clone() и final полях.
Метод clone() несовместим с final полями. Если вы попробуете клонировать final поле компилятор остановит вас. Единственное решение — отказаться от final.
Ну и пример использования clone():Консоль: Как видите, изменение объекта a повлекло за собой изменение объекта c, а вот с b всё в порядке.
- Где и как вы можете использовать закрытый конструктор?
Например в качестве паттерна Синглетон. В том же классе создается статический метод. Где и создается экземпляр класса, конечно если он уже не создан, тогда он просто возвращается методом.
- Что такое конструктор по умолчанию?
В Java если нет явным образом опредёленных конструкторов в классе, то компилятор использует конструктор по умолчанию, опредёленный неявным способом, который аналогичен «чистому», конструктору по умолчанию. Конструктор по умолчанию — это довольно простая конструкция, которая сводится к созданию для типа конструктора без параметров. Так, например, если при объявлении нестатического класса не объявить пользовательский конструктор (не важно, с параметрами или без них), то компилятор самостоятельно сгенерирует конструктор без параметров. Некоторые программисты явным образом задают конструктор по умолчанию по привычке, чтобы не забыть в дальнейшем, но это не обязательно
В Java если производный класс не вызывает явным образом конструктор базового класса (в Java используя super() в первой строчке), то конструктор по умолчанию вызывается неявно. Если базовый класс не имеет конструктора по умолчанию, то это считается ошибкой.
- Опишите метод Object.finalize().
Метод finalize(). Java обеспечивает механизм, который является аналогичным использованию деструкторов в С ++, который может использоваться для того, чтобы произвести процесс очистки перед возвращением управления операционной системе.
Применяя метод finalize(), можно определять специальные действия, которые будут выполняться тогда, когда объект будет использоваться сборщиком мусора. Данный метод вызывается при уничтожении объекта автоматическим сборщиком мусора (garbage collector). В классе Object он ничего не делает, однако в классе-наследнике позволяет описать все действия, необходимые для корректного удаления объекта, такие как закрытие соединений с БД, сетевых соединений, снятие блокировок на файлы и т.д. В обычном режиме напрямую этот метод вызывать не нужно, он отработает автоматически. Если необходимо, можно обратиться к нему явным образом.
Его синтаксис:
protected void finalize( ) throws Throwable
Ссылки не являются собранным мусором; только объекты — собранный мусор.
- Чем отличаются слова final, finally и finalize?
final — Нельзя наследоваться от файнал класса. Нельзя переопределить файнал метод. Нельзя изменить сначение файнал поля.
finally — используется при обработке ошибок, вызывается всегда, даже если произошла ошибка(кроме System. exit(0)). Удобно использовать для освобождения ресурсов.
finalize() — вызывается перед тем как сборщик мусора будет проводить освобождение памяти. Не рекомендуется использовать для освобождения системных ресурсов, так как не известно когда сборщик мусора будет производить свою очистку. Вообще данный метод мало кто использует. Единственно что можно использовать этот метод для закрытия ресурса что должен работать на протяжении всей работы программы и закрываться по ее окончанию. Еще можно использовать метод для защиты от так называемых «дураков», проверять, освобождены ли ресурсы, если нет, то закрыть их.Java core (часть 3).
Методы equals и hashCode в Java
Какую роль играют методы equals и hashCode, нужно ли их переопределять и как правильно их использовать.
Для начала разберёмся, что это за методы и в чём между ними разница.
- equals – возвращает true если объекты одинаковые;
- hashCode – вычисляет хэш код объекта.
По определению если два объекта одинаковы, то у них должны быть одинаковы и хэш коды. Да, это так. Но, хэш функции пока ещё не могут дать 100% гарантию того, что даже при сложном алгоритме их вычисления рано или поздно не появятся два разных объект с одинаковыми хэш кодами (то есть не возникнет так называемая коллизия). Поэтому метод hashCode не является достаточно надёжным средством для определения идентичности объектов и в дополнение к нему существует метод equals, которым и следует пользоваться при сравнении объектов.
Методы equals и hashCode определены и реализованы в классе Object.
Хороший стиль разработки предполагает переопределение этих методов. Но, многие программисты, к сожалению, этим пренебрегают и нередко можно столкнуться с тем, что даже на самом верхнем уровне абстракции в сложной иерархии классов equals и hashCode остаются неизменными.
И не было бы ничего страшного если бы equals и hashCode в Object могли каким-то образом учитывать содержимое класса, но такой возможности у них нет. Более того метод hashCode в Object реализован так, что даже у одного и того же объекта при каждом запуске программы хэш код будет разным. Поэтому, даже если возникнет необходимость просто сравнить хзш коды объектов, то получить адекватный результат при таком сравнении будет почти невозможно.
Как быть?
Первый и самый лучший способ. При создании собственных классов переопределять методы equals и hashCode. Как при переопределении будет учитываться структура класса, хранящиеся в нём данные и другие факторы, каждому программисту необходимо решать индивидуально в зависимости от задачи. Но, в любом случае собственная реализация equals и hashCode должна быть.
Если же её по каким-то причинам нет, и вы или ваш коллега оставляете всё как было изначально в Object, то лучше следовать второму способу. А, именно не использовать методы equals и hashCode вовсе. Лучше отказаться от использования методов, чем работать с некорректными результатами их выполнения.
Коллекции Java — hashCode () и equals ()
пакет crunchify. ком . учебные пособия ;
/ **
*
* @author Crunchify.com
* Как переопределить метод equals () в Java?
* Как переопределить метод hasCode () в Java?
* версия: 1.2
*
* /
общественности учебный класс CrunchifyImplementEqualsHashCode {
общественности статический недействительным main ( Строка [ ] аргументы ) {
CrunchifyImplementEqualsHashCode crunchifyTest знак равно новый CrunchifyImplementEqualsHashCode ( ) ;
Хрустеть один знак равно новый Хруст ( 1 ) ;
Хрустеть два знак равно новый Хруст ( 1 ) ;
crunchifyTest . test1 ( один , два ) ;
Хруст три знак равно новый Хруст ( 1 ) ;
Хрустеть четыре знак равно новый Хрустить ( 2 ) ;
crunchifyTest . test2 ( три , четыре ) ;
}
общественности недействительным test1 ( Crunchify one , Хрустеть двумя ) {
если ( один . равно ( два ) ) {
Система. вне. println ( «Тест1: один и два равны» ) ;
} еще {
Система. вне. println ( «Тест1: Один и Два не равны» ) ;
}
}
общественности недействительным test2 ( Хруст три , Хрустеть четверо ) {
если ( три . равно ( четыре ) ) {
Система. вне. println ( «Test2: три и четыре равны» ) ;
} еще {
Система. вне. println ( «Test2: три и четыре не равны» ) ;
}
}
}
учебный класс Crunchify {
частный ИНТ стоимость ;
Crunchify ( int val ) {
значение знак равно val ;
}
общественности ИНТ getValue ( ) {
вернуть стоимость ;
}
// Метод переопределяет или реализует метод, объявленный в супертипе.
@Override
общественности логический равно ( объект о ) {
// нулевая проверка
если ( о == ноль ) {
вернуть ложь ;
}
// проверка этого экземпляра
если ( это == о ) {
вернуть правда ;
}
// Проверка экземпляра и проверка фактического значения
если ( ( о экземпляр Хрустеть ) && (((Crunchify) o) . getValue () == this.value)) {
вернуть истину;
} еще {
вернуть ложь ;
}
}
@Override
общественности ИНТ hashCode ( ) {
ИНТ результат знак равно ;
результат знак равно ( int ) ( значение / 11 ) ;
вернуть результат ;
}
}
Hashcode java для чего нужен – Тарифы на сотовую связь
64 пользователя считают данную страницу полезной.
Информация актуальна! Страница была обновлена 16.12.2019
В классе Object, который является родительским классом для объектов java, определен метод hashCode(), позволяющий получить уникальный целый номер для данного объекта. Когда объект сохраняют в коллекции типа HashSet, то данный номер позволяет быстро определить его местонахождение в коллекции и извлечь. Функция hashCode() объекта Object возвращает целое число int, размер которого равен 4-м байтам и значение которого располагается в диапазоне от -2 147 483 648 до 2 147 483 647.
Рассмотрим простой пример HashCodeTest.java, который в консоли будет печатать значение hashCode.
Значение hashCode программы можно увидеть в консоли.
По умолчанию, функция hashCode() для объекта возвращает номер ячейки памяти, где объект сохраняется. Поэтому, если изменение в код приложения не вносятся, то функция должна выдавать одно и то же значение. При незначительном изменении кода значение hashCode также изменится.
Функция сравнения объектов equals()
В родительском классе Object наряду с функцией hashCode() имеется еще и логическая функция equals(Object)/ Функция equals(Object) используется для проверки равенства двух объектов. Реализация данной функции по умолчанию просто проверяет по ссылкам два объекта на предмет их эквивалентности.
Рассмотрим пример сравнения двух однотипных объектов Test следующего вида :
Создадим 2 объекта типа Test с одинаковыми значениями и сравним объекты с использованием функции equals().
Результат выполнения программы будет выведен в консоль :
Не трудно было догадаться, что результат сравнения двух объектов вернет «false». На самом деле, это не совсем верно, поскольку объекты идентичны и в real time application метод должен вернуть true. Чтобы достигнуть этого корректного поведения, необходимо переопределить метод equals() объекта Test.
Вот теперь функция сравнения equals() возвращает значение «true». Достаточно ли этого? Попробуем добавить объекты в коллекцию HashSet и посмотрим, сколько объектов будет в коллекции? Для этого в в метод main примера EqualsExample добавим следующие строки :
Однако в коллекции у нас два объекта.
Поскольку Set содержит только уникальные объекты, то внутри HashSet должен быть только один экземпляр. Чтобы этого достичь, объекты должны возвращать одинаковый hashCode. То есть нам необходимо переопределить также функцию hashCode() вместе с equals().
Вот теперь все будет корректно выполняться – для двух объектов с одинаковыми параметрами функция equals() вернет значение «true», и в коллекцию попадет только один объект. В консоль будет выведен следующий текст работы программы :
Таким образом, переопределяя методы hashCode() и equals() мы можем корректно управлять нашими объектами, не допуская их дублирования.
Использование библиотеки commons-lang.jar для переопределения hashCode() и equals()
Процесс формирования методов hashCode() и equals() в IDE Eclipse автоматизирован. Для создания данных методов необходимо правой клавишей мыши вызвать контекстное меню класса (кликнуть на классе) и выбрать пункт меню Source (Class >> Source), в результате которого будет открыто следующее окно со списком меню для класса.
Выбираем пункт меню «Generate hachCode() and equals()» и в класс будут добавлены соответствующие методы.
Библиотека Apache Commons включает два вспомогательных класса HashCodeBuilder и EqualsBuilder для вызова методов hashCode() и equals(). Чтобы включить их в класс необходимо внести следующие изменения.
Примечание : желательно в методах hashCode() и equals() не использовать ссылки на поля, заменяйте их геттерами. Это связано с тем, что в некоторых технологиях java поля загружаются при помощи отложенной загрузки (lazy load) и не доступны, пока не вызваны их геттеры.
Скачать пример
Исходный код рассмотренного примера в виде проекта Eclipse можно скачать здесь (263 Kб).
Методы hashCode ( ) и equals ( ) — базовые методы языка Java. На их основе работают коллекции. Оба эти метода объявлены в классе java . lang . Object . Дочерние классы могут, а в некоторых случаях даже должны переопределять их. Про эти методы я уже как-то писал, но нужно упомянуть об этом ещё раз.
Метод hashCode()
Объявление в классе Object :
Метод hashCode ( ) возвращает число, которое является хеш-кодом объекта. Реализация по умолчанию в классе Object обычно возвращает адрес объекта, но это в спецификации Java это не закреплено, так что некоторые реализации Java-машин вполне могут возвращать что-нибудь другое.
Метод equals()
Объявление в классе Object :
Возвращает true , если obj равен этому объекту и false в противном случае. Реализация по умолчанию в классе Object просто сравнивает ссылки на объекты, то есть возвращает true в том случае, если обе ссылки указывают на один и тот же объект.
Соглашение между реализациями hashCode() и equals()
В большинстве случаев реализации hashCode ( ) и equals ( ) , которую ваши классы наследуют от Object вам не подойдёт, так как вам нужно, чтобы при вызове equals ( ) сравнивались не ссылки на объекты, а значения полей объектов. Именно поэтому вам нужно будет переопределять equals ( ) для тех классов, которые будут использоваться в качестве ключей коллекций Map и Set . При этом нужно иметь в виду, что при переопределении equals ( ) нужно всегда переопределять hashCode ( ) так, чтобы сохранялись следующие соглашения:
- Если x . equals ( y ) возвращает true , то hashCode ( ) у обоих экземпляров объектов должны возвращать одинаковые значения.
- Но если x . hashCode ( ) == y . hashCode ( ) , то вовсе не обязательно, чтобы x . equals ( y ) возвращало true , оно может возвращать как true , так и false .
Как писать hashCode() и equals()?
В большинстве > hashCode ( ) и equals ( ) , где вам нужно будет только указать поля, которые необходимо учитывать при генерации кода этих методов. При этом вам зачастую на выбор будет предоставлено несколько вариантов генераций:
- Генерация на чистом Java, как оно было раньше
- Генерация с помощью библиотеки Apache Commons Lang.
- Генерация с помощью класса java . util . Objects , который входит в состав Java 7. В классе java . util . Objects есть специальные методы public static int hash ( Object . . . values ) , public static boolean deepEquals ( Object a , Object b ) и public static boolean equals ( Object a , Object b ) . Эти методы пришли в Java из библиотеки Guava. Методы с приставкой deep отличаются от обычных тем, что они заходят внутрь массивов и проходят по их элементам, об этом написано чуть ниже.
- Генерация с помощью Guava, где есть методы, аналогичные методам из java . util . Objects .
Всегда имеет смысл посмотреть на сгенерированный IDE код для общего развития. Здесь прослеживается следующая связь:
- Все реализации коллекций и Map -ов в Java имеют переопределённые методы hashCode ( ) и equals ( ) , которые пробегаются по своим элементам для получения результата.
- Массивы в Java не переопределяют hashCode ( ) и equals ( ) . Они используют реализацию из класса Object , которая сравнивает ссылки. Поэтому при построении hashCode ( ) нужно пользоваться статическими методами hashCode ( ) и deepHashCode ( ) из класса java . util . Arrays . При написании методов equals нужно аналогично использовать методы equals ( ) и deepEquals ( ) из класса java . util . Arrays . Методы с приставкой deep здесь отличаются от обычных тем, что в случае, если массив(ы) содержать в качестве элементов другие массивы, то методы без приставки deep будут возвращать значения, основанные на методе из Object , а с приставкой deep будут заходить внутрь этого вложенного массива и проходиться по его элементам.
Пока писал эту статью понял, что в моём учебнике по Java не описаны методы класса Object . Странно. Как же я смог их пропустить? Надо как-то восполнить этот пробел.
Прежде чем пытаться понять методы equals() и hashCode(), необходимо вспомнить несколько фактов: в Java при сравнении ссылочных переменных сравниваются не сами объекты, а ссылки на объекты, и что все объекты унаследованы от класса Object, который содержит реализацию методов equals() и hashCode() по умолчанию.
Для решения задачи сравнения ссылочных переменных существует стандартное решение – метод equals(). Цель данного метода – определить идентичны ли объекты внутри, сравнив их внутреннее содержание. У класса Object есть своя реализация метода equals, которая просто сравнивает ссылки:
Порой такой реализации бывает не достаточно, поэтому, при необходимости чтобы разные объекты с одинаковым содержимым рассматривались как равные, надо переопределить метод equals() учитывая поля, которые должны участвовать в сравнении объектов. Ведь только разработчик класса знает, какие данные важны, что учитывать при сравнении, а что – нет.
У метода equals() есть большой минус – он слишком медленно работает. Для этого был придуман метод hashCode(). Для каждого объекта данный метод возвращает определенное число. Какое именно – это тоже решает разработчик класса, как и в случае с методом equals().
Стандартная реализация метода hashCode() в классе Object:
Вместо того чтобы сравнивать объекты, будем сравнивать их hashCode, и только если hashCode-ы равны, сравнивать объекты посредством equals().
Разработчик, который реализует функцию hashCode(), должен помнить следующее:
1) у двух разных объектов может быть одинаковый hashCode ;
2) у одинаковых объектов (с точки зрения equals()) должен быть одинаковый hashCode ;
3) хеш-коды должны быть выбраны таким образом, чтобы не было большого количества различных объектов с одинаковыми hashCode. Ситуация, когда у различных объектов хеш-коды совпадают называется коллизией.
Важное замечание: при переопределении метода equals(), обязательно нужно переопределить метод hashCode(), с учетом трех вышеописанных правил (Переопределил equals — переопредели и hashCode).
Дело в том, что коллекции в Java перед тем как сравнить объекты с помощью equals всегда ищут/сравнивают их с помощью метода hashCode(). И если у одинаковых объектов будут разные hashCode, то объекты будут считаться разными — до сравнения с помощью equals() просто не дойдет.
Большинство современных IDE помогают переопределять методы equals() и hashCode(). К примеру, в Intellij Idea можно набрать комбинацию клавиш Alt+Ins, и в выпадающем меню Generate выбрать пункт equals and hashCode.
Java hashCode() и equals() — urvanov.
ruМетоды hashCode() и equals() — базовые методы языка Java. На их основе работают коллекции. Оба эти метода объявлены в классе java.lang.Object. Дочерние классы могут, а в некоторых случаях даже должны переопределять их. Про эти методы я уже как-то писал, но нужно упомянуть об этом ещё раз.
Метод hashCode()
Объявление в классе Object:
Метод hashCode() возвращает число, которое является хеш-кодом объекта. Реализация по умолчанию в классе Object обычно возвращает адрес объекта, но это в спецификации Java это не закреплено, так что некоторые реализации Java-машин вполне могут возвращать что-нибудь другое.
Метод equals()
Объявление в классе Object:
public boolean equals(Object obj)
public boolean equals(Object obj) |
Возвращает true, если obj равен этому объекту и false в противном случае. Реализация по умолчанию в классе Object просто сравнивает ссылки на объекты, то есть возвращает true в том случае, если обе ссылки указывают на один и тот же объект.
Соглашение между реализациями hashCode() и equals()
В большинстве случаев реализации hashCode() и equals(), которую ваши классы наследуют от Object вам не подойдёт, так как вам нужно, чтобы при вызове equals() сравнивались не ссылки на объекты, а значения полей объектов. Именно поэтому вам нужно будет переопределять equals() для тех классов, которые будут использоваться в качестве ключей коллекций Map и Set. При этом нужно иметь в виду, что при переопределении equals() нужно всегда переопределять hashCode() так, чтобы сохранялись следующие соглашения:
- Если x.equals(y) возвращает true, то hashCode() у обоих экземпляров объектов должны возвращать одинаковые значения.
- Но если x.hashCode() == y.hashCode(), то вовсе не обязательно, чтобы x. equals(y) возвращало true, оно может возвращать как true, так и false.
Как писать hashCode() и equals()?
В большинстве IDE уже есть готовые генераторы hashCode() и equals(), где вам нужно будет только указать поля, которые необходимо учитывать при генерации кода этих методов. При этом вам зачастую на выбор будет предоставлено несколько вариантов генераций:
- Генерация на чистом Java, как оно было раньше
- Генерация с помощью библиотеки Apache Commons Lang.
- Генерация с помощью класса java.util.Objects, который входит в состав Java 7. В классе java.util.Objects есть специальные методы public static int hash(Object… values), public static boolean deepEquals(Object a, Object b) и public static boolean equals(Object a, Object b). Эти методы пришли в Java из библиотеки Guava. Методы с приставкой deep отличаются от обычных тем, что они заходят внутрь массивов и проходят по их элементам, об этом написано чуть ниже.
- Генерация с помощью Guava, где есть методы, аналогичные методам из java.util.Objects.
Всегда имеет смысл посмотреть на сгенерированный IDE код для общего развития. Здесь прослеживается следующая связь:
- Все реализации коллекций и Map-ов в Java имеют переопределённые методы hashCode() и equals(), которые пробегаются по своим элементам для получения результата.
- Массивы в Java не переопределяют hashCode() и equals(). Они используют реализацию из класса Object, которая сравнивает ссылки. Поэтому при построении hashCode() нужно пользоваться статическими методами hashCode() и deepHashCode() из класса java.util.Arrays. При написании методов equals нужно аналогично использовать методы equals() и deepEquals() из класса java.util.Arrays. Методы с приставкой deep здесь отличаются от обычных тем, что в случае, если массив(ы) содержать в качестве элементов другие массивы, то методы без приставки deep будут возвращать значения, основанные на методе из Object, а с приставкой deep будут заходить внутрь этого вложенного массива и проходиться по его элементам.
P. S.
Пока писал эту статью понял, что в моём учебнике по Java не описаны методы класса Object. Странно. Как же я смог их пропустить? Надо как-то восполнить этот пробел.
Поделиться:
Переопределение в Kotlin. Курс «ООП в Kotlin»
Наследование в Kotlin начинается не с объявления программистом какого-то класса родительским. Уже самый первый родительский класс является наследником общего для всех суперкласса Any. Это наследование происходит по-умолчанию, поэтому его можно не указывать. В случае явного указания заголовок класса выглядит примерно так:
class NumInc(n: Int, gap: Int): Any() { ...
Поскольку дочерние классы наследуют свойства и функции родительского, то все, что есть у Any, есть у всех остальных классов. Не важно, непосредственно это класс-наследник или наследник наследника.
Класс Any включает три функции-члена – equals(), toString(), hashСode(). Если мы создадим пустой класс, у его объектов все-равно будут методы, унаследованные от Any:
fun main() { val a = Days() val b = Days() println(a. equals(b)) // или a == b println(a.hashCode()) println(b.hashCode()) println(a.toString()) println(a) } class Days {}
Пример выполнения программы:
false 1018547642 1456208737 Days@3cb5cdba Days@3cb5cdba
Метод toString() в данном случае можно не вызывать, так как функция println() делает это сама. Этот метод отвечает за преобразование объекта, каких-то данных о нем, к строке. Он должен возвращать строку. В классе Any запрограммирован возврат строки, содержащей имя класса и через «собаку» по всей видимости адрес объекта в памяти. Нас может не устраивать такое положение дел, лучше бы, чтобы toString() выводит что-нибудь более полезное.
Для этого нам потребуется переопределить в нашем классе функцию-член родительского класса. Это значит, что мы создаем в своем классе метод с таким же именем, что и в родительском, а тело метода пишем свое. И когда объект будет вызывать этот метод, будет использована функция-член своего класса, а не родительского.
Если в классе начать писать имя метода родительского класса, то IntelliJ IDEA сама предложит его для переопределения.
После подтверждения код класса будет выглядеть так:
open class NumInc(n: Int, gap: Int) { var number = n var step = gap fun inc() {number += step} fun dec() {number -= step} override fun toString(): String { return super.toString() } }
Слово override говорит, что данная функция переопределяет родительскую. String после двоеточия – тип возвращаемых функцией данных, должна возвращаться строка.
В теле автоматически сгенерированной функции происходит обращение к одноименной функции-члену родительского класса. Происходит это через ключевое слово super, которое обозначает родительский класс. Из него мы получаем строку, строка возвращается в функцию toString() класса NumInc, а уже отсюда с помощью return она передается в место, откуда вызывался данный метод.
В таком варианте, несмотря на то, что мы переопределили функцию в дочернем классе, ничего не изменилось. Как по большому счету выполнялось тело функции родительского класса, так оно и будет выполняться. Просто вызов идет не напрямую, а опосредовано через дочерний класс.
Пусть мы хотим, чтобы метод toString() возвращал информацию о текущих значениях свойств объектов. Для этого в теле метода удалим вызов аналогичного метода родительского класса и напишем свое тело:
open class NumInc(n: Int, gap: Int) { var number = n var step = gap fun inc() {number += step} fun dec() {number -= step} override fun toString(): String { val n = "number = $number" val s = "step = $step" return "$n \n $s" } }
Если main() будет таким,
fun main() { val a = NumInc(0, 1) val b = NumInc(12, 2) println(a.toString()) println(b) }
результат выполнения таким.
number = 0 step = 1 number = 12 step = 2
Мы можем переопределять не только методы класса Any, но и своего родительского в его дочернем. Пусть нам нужен класс подобный NumInc, но функции inc() и dec() которого уменьшают и увеличивают number
на двойной шаг. В этом случае, если этот класс будет дочерним от NumInc, нам придется переопределить функции inc() и dec():
class NumDouble(n: Int, gap: Int): NumInc(n, gap) { override fun inc() { number += step * 2 } override fun dec() { number -= step * 2 } }
Однако мы не сможем этого сделать до тех пор, пока в родительском классе не разрешим переопределять методы с этими именами. Для этого следует объявить как открытые с помощью слова open:
open class NumInc(n: Int, gap: Int) { var number = n var step = gap open fun inc() {number += step} open fun dec() {number -= step} ... }
Другими словами, в Kotlin, чтобы иметь возможность переопределять методы, недостаточно, чтобы их класс был открытым. Необходимо дополнительно указывать такую возможность для каждого метода. По-умолчанию методы вместо модификатора open имеют модификатор final. Мы его не пишем.
Однако, если дочерний класс переопределяет метод родительского, то в нем open не пишется. Вместо этого стоит override. При этом метод остается открытым. Если у дочернего будет свой дочерний, то он сможет переопределить такой метод. Если требуется исключить возможность дальнейшего переопределения, то перед override пишут final.
В примере на скрине мы объявили класс NumDouble открытым и создали от него дочерний NumD2. В нем мы можем переопределить метод inc() и не можем сделать это с dec(), потому что в NumDouble объявили метод финальным.
Бывает так, что код переопределяемого метода не сильно отличается от того, что используется в родительском классе. Нам нужно его лишь дополнить. В этом случае из тела метода дочернего класса, вызывается родительский метод через слово super. После того, как он возвращает поток выполнения в метод дочернего класса, здесь выполняется дополнительный код. Подобное мы уже видели, переопределяя toString().
Давайте сделаем это в нашем классе NumDouble, хотя в данном случае большого смысла в этом нет. Тело класса очень простое.
open class NumDouble(n: Int, gap: Int): NumInc(n, gap) { override fun inc() { super.inc() number += step } override fun dec() { super.dec() number -= step } }
Здесь мы изменяем number
на один шаг через родительский метод, потом делаем это еще раз уже в дочернем.
Следует отметить, что переопределять можно не только функции-члены класса, но и свойства. Однако нельзя изменить их тип. Поэтому такое переопределение обычно имеет место, когда используются инициирующие значения для полей, и в дочернем классе эти значения должны быть не такими, каковы они в родительском.
open class NumInc { open var number = 0 open var step = 1 open fun inc() {number += step} open fun dec() {number -= step} } open class NumDouble: NumInc() { override var number = 1 override var step = 2 }
Практическая работа:
Пусть свойство step
родительского класса NumInc имеет сеттер, проверяющий число на неравенство нулю (нулевой шаг не допустим). В дочернем классе NumDouble такая проверка не нужна. Наследуется ли сеттер, следует ли его переопределять?
PDF-версия курса с ответами к практическим работам
Приложение для Android «Kotlin. Курс»
250+ вопросов с собеседований по Java
Сайт DOU.UA поинтересовался у СТО и опытных специалистов, проводящих интервью с Java-разработчиками, какие теоретические вопросы они задают и какие задачи предлагают решить кандидатам. Представляем вам перевод этой статьи.
Junior
Общие вопросы
1. Какие шаблоны проектирования вы знаете? Расскажите о двух шаблонах, которые использовали в работе.
Java Core
2. Какие есть типы данных в Java?
3. Чем отличается объект от примитивных типов данных?
4. В чем разница передачи параметров по ссылке и по значению?
5. Что такое JVM, JDK, JRE?
6. Зачем используют JVM?
7. Что такое bytecode?
8. Каковы признаки JavaBean?
9. Что такое OutOfMemoryError?
10. Что такое стектрейс? Как его получить?
11. Назовите все методы класса object.
12. В чем отличие между try-with-resources и try-catch-finally при работе с ресурсами?
13. Что такое конструкторы? Какие типы вы знаете?
14. Что такое побитовые операции?
15. Объекты каких стандартных классов immutable в Java?
16. Дайте краткую характеристику immutable object. Зачем они нужны?
17. Как создать immutable object?
18. Каковы преимущества immutable object по сравнению с обычными объектами?
ООП
19. Что такое ООП? Назовите принципы с примерами.
20. В чем преимущества ООП по сравнению с процедурным программированием?
21. В чем состоит главная особенность ООП?
22. Расскажите, какие преимущества мы получаем с применением ООП?
23. Расскажите, какие есть недостатки в ООП?
24. Расскажите о принципе наследования в ООП. Зачем он нужен?
25. Дайте определение принципа полиморфизм в ООП. Как работает полиморфизм?
26. Что такое статический и динамический полиморфизм?
27. Дайте определение принципа абстракции в ООП.
28. Какие элементы языка отвечают за инкапсуляцию?
29. Какие элементы языка отвечают за наследование?
30. Какие элементы языка отвечают за полиморфизм?
31. Что такое SOLID? Приведите примеры.
32. Что такое перегрузка (overloading) метода?
33. Что такое переопределение (override) метода?
34. Что такое класс, объект, интерфейс?
35. Что такое класс POJO? Приведите пример такого класса.
36. Какие элементы могут содержать класс?
37. Дайте определение объекта.
38. Расскажите о наследовании в Java. Каковы особенности использования ключевого слова super?
39. Что такое сигнатура метода? Приведите примеры правильных и неправильных сигнатур.
40. Можно ли в конструкторе применять return?
41. Можно ли в конструкторе выполнить исключение (exception)?
42. Из каких элементов состоит заголовок класса? Напишите пример.
43. Из каких элементов состоит заголовок метода? Напишите пример.
44. Создайте в объекте-наследнике конструктор по умолчанию, если в базовом классе он не определен (но определен другой конструктор).
45. Когда применяется ключевое слово this?
46. Что такое инициализатор?
47. Для наследования класса public class Child extends Parent напишите порядок инициализации объекта.
48. Какие отношения между классами (объектами) вы знаете?
49. Какие вы знаете ассоциативные связи между объектами?
50. Что такое модификаторы доступа в Java? Назовите их. Для чего они используются?
51. Назовите основную особенность статических переменных и методов.
52. Какие основные ограничения действуют на статические переменные и методы?
53. Что означает ключевое слово static? Может ли статический метод быть переопределенным или перегруженным?
54. Может ли метод быть статическим и абстрактным одновременно?
55. Можно ли использовать статические методы внутри обычных? А наоборот? Почему?
56. Что означает ключевое слово final?
57. Что такое abstract? Абстрактный класс? Абстрактный метод?
58. Что такое interface? Может ли быть final interface?
59. В чем разница между абстрактным классом и интерфейсом в Java?
60. Где можно инициализировать статические поля?
61. Что такое анонимные классы?
62. Что такое примитивные классы?
63. Что такое класс «обертка» (wrapper)?
64. Что такое Nested class? Когдаон используется?
65. Какие модификаторы доступа могут быть у класса?
Библиотеки и стандарты
67. Что такое Hibernate? В чем разница между JPA i Hibernate?
68. Что такое каскадность? Как она используется в Hibernate?
69. Может ли entity-класс быть абстрактным классом?
70. Что такое entity manager? За что отвечает?
71. Что такое класс Assert? Зачем и как его использовать?
String
72.Дайте характеристику String в Java.
73.Какие есть способы создания объекта String? Где он создается?
74. Как сравнить две строки в Java и / или отсортировать их?
75. Предложите алгоритм преобразования строки в символ. Напишите соответствующий код.
76. Как превратить строку в массив байтов и обратно? Напишите соответствующий код.
77. Что такое строковый пул и зачем он нужен?
78. Какие GOF-шаблоны применяются в строковом пуле?
79. Как разделить строку на части? Напишите соответствующий код.
80. Почему для хранения пароля массив символов лучше строки?
81. Чем отличаются String, StringBuffer и StringBuilder?
Enum
82. Дайте краткую характеристику Enum в Java.
83. Может ли Enum реализовывать (implement) интерфейс?
84. Может ли Enum расширить (extends) класс?
85. Можно ли создать Enum без экземпляров объектов?
86. Можем ли мы переопределить метод toString() для Enum?
87. Что будет, если мы не будем переопределять метод toString() для Enum?
88. Можем ли мы указать конструктор внутри Enum?
89. В чем разница между == и equals()?
90. Что делает метод ordinal() в Enum?
91. Можно ли использовать Enum с TreeSet или TreeMap в Java?
92. Как связаны методы ordinal () и compareTo() в Enum?
93. Напишите пример Enum.
94. Можно ли использовать Enum в switch case?
95. Как получить все имеющиеся значения в экземпляре Enum?
Stream API
96. Что такое Stream в Java?
97. Назовите основные свойства транзакций.
98. Какие есть уровни изоляции транзакций?
99. Чем отличаются Statement и PreparedStatement?
Collections
100. Расскажите о итераторах и их применении.
101. Какова иерархия коллекций в Java Collection Framework?
102. Каково внутреннее строение ArrayList?
103. Каково внутреннее строение LinkedList?
104. Каково внутреннее строение HashMap?
105. Чем ArrayList отличается от LinkedList?
106. Чем ArrayList отличается от HashSet?
107. Зачем в Java такое многообразие имплементации динамического массива?
108. Зачем в Java такое многообразие имплементации key-value storage?
109. Как отсортировать коллекцию элементов?
Class Object. Equals and HashCode
110. Дайте краткую характеристику class object в Java.
111. Для чего используют Equals and HashCode в Java?
112. Расскажите о контракте между Equals and HashCode в Java.
113. Какие условия выдвигаются относительно переопределения соглашения при переопределении Equals?
114. Что будет, если не переопределить Equals and HashCode?
115. Какие значения мы получим, если у нас не перераспределены Equals and HashCode?
116. Почему симметричность выполняется, только если x.equals(y) возвращает true?
117. Что такое коллизия в HashCode? Как с ней бороться?
118. Что будет, если элемент, участвующий в контракте с HashCode, меняет свое значение?
119. Напишите методы Equals and HashCode для класса Student, который состоит из полей String name и int age.
120. В чем разница применения if (obj instanceof Student) и if (getClass () == obj.getClass ())?
121.Дайте краткую характеристику метода clone().
122. В чем заключается особенность работы метода clone() с полями объекта типа ссылки?
Exceptions
123. Дайте определение понятию exception (исключительная ситуация).
124. Какие особенности использования оператору try … catch вы знаете?
125. В чем разница между error и exception?
126. Чем отличаются checked и unchecked, exception, throw, throws.
127. Какова иерархия исключений?
128. Что такое checked и unchecked exception?
129. Нужно ли проверять checked exception?
130. О чем говорит ключевое слово throws и как его использовать?
131. Какие возможные способы обработки исключений вы знаете?
132. Напишите пример перехвата и обработки исключения в блоке try-catch метода.
133. Напишите пример перехвата и обработки исключения в секции throws-метода и передачи вызывающему методу.
134. Напишите пример перехвата и обработки исключения с использованием собственных исключений.
135. Какие есть правила для проверки исключений при наследовании?
136. Не могли бы вы написать код, если блок finally не будет выполнен?
137. Напишите пример обработки нескольких исключений в одном блоке catch.
138. Какой оператор позволяет принудительно выбросить исключение? Напишите пример.
139. Может ли метод main выбросить throws-исключение? Если да — куда передаст?
140. Напишите пример try with resources.
Многопоточность
141. Какие средства для работы с многопоточностью вы знаете?
142. Что такое процесс и поток? Чем отличается процесс от потока?
143. Расскажите о синхронизации между потоками. Для чего используют методы wait(), notify() — notifyAll(), join()?
144. Как остановить поток?
145. Как обмениваться данными между потоками?
146. Чем класс Thread отличается от интерфейса Runnable?
147. Есть потоки Т1, Т2 и Т3. Как реализовать их последовательное выполнение?
Практические задания
148. Matrix Diagonal Sum (задача с Leetcode).
149. Move Zeroes (задача с Leetcode).
150. Given List<String> names. Удалите первую букву из каждого имени и верните отсортированный список.
151. Перевернуть массив.
152. Проверить, является ли строка палиндромом.
153. Написать простой алгоритм сортировки (Bubble, Selection или Shuttle). Как его можно улучшить?
154. Напишите алгоритм (последовательность действий) составления литерала типа int и литерала типа byte. Объясните, что происходит с памятью.
Middle
Общие вопросы
1. В чем преимущества и недостатки ООП, если сравнивать с процедурным / функциональным программированием?
2. Чем отличается агрегация от композиции?
3. Какие паттерны GoF вы использовали на практике? Приведите примеры.
4. Что такое прокси-объект? Приведите примеры.
5. Какие нововведения анонсированы в Java 8?
6. Что такое High Cohesion и Low Coupling? Приведите примеры.
ООП
7. Каким образом можно реализовать множественное наследование в Java?
8. В чем разница между методами final, finally и finalize()?
Java Core
9. В чем разница между статическим и динамическим связыванием в Java?
10. Можно ли использовать private или protected переменные в interface?
11. Что такое Classloader и для чего используется?
12. Что такое Run-Time Data Areas?
13. Что такое immutable object?
14. В чем особенность класса String?
15. Что такое ковариантность типов?
16. Какие есть методы в классе Object?
17. Приведите примеры удачного и неудачного использования Optional.
18. Можно ли объявлять main method как final?
19. Можно ли импортировать те же package / class дважды? Какие последствия?
20. Что такое Casting? Когда можем получить исключение ClassCastException?
21. Почему современные фреймворки используют в основном только unchecked exceptions?
22. Что такое static import?
23. Какая связь между методами hashCode() и equals()?
24. Когда используют BufferedInputStream и BufferedOutputStream классы?
25. Чем отличаются между собой классы java.util.Collection и java.util.Collections?
26. В чем отличие между Enumeration и Iterator?
27. В чем разница между итераторами fail-fast и fail-safe?
28. Зачем нужен модификатор transient?
29. Как влияют на сериализацию модификаторы static и final?
30. Каковы особенности использования интерфейса Cloneable?
31. Каковы особенности использования интерфейса AutoCloseable?
32. Что такое FunctionInterface и чем он отличается от обычного интерфейса?
33. Что такое и зачем нужны Atomic types?
34. Что такое Happens-before? Каковы особенности использования ключевого слова volatile?
35. Расскажите о Heap и Stack памяти в Java. В чем разница между ними? Где хранятся примитивы?
36. Чем отличается stack от heap памяти? Когда и какая область памяти резервируется? Зачем такое разделение нужно?
37. Каковы принцип работы и области памяти Garbage Collector?
38. Как работает Garbage Collector? Расскажите о Reference counting и Tracing.
39. Расскажите о механизме работы autoboxing в Java.
40. Как реализована сериализация в Java? Где мы можем ее увидеть?
41. Расскажите, в чем разница между WeakReference и SoftReference?
42. Что такое generics? Для чего они нужны? Какую проблему решают?
43. Что такое PECS и как используется? Приведите примеры.
44. Зачем на практике могут понадобиться immutable объекты?
Библиотеки и инструменты
45. Чем полезны инструменты Maven, Ant, Gradle?
46. Что такое Unit Tests? Чем класс JUnit.Assert отличается от ключевого слова assert?
47. Что такое и зачем нужен Spring core? Раскройте понятие Inversion of Control и Dependency Injection.
48. Как «под капотом» работает @Transactional?
49. Как «под капотом» работает Spring?
50. Что такое и зачем нужен Hibernate? Раскройте понятие ORM.
51. Что такое и когда возникает LazyLoadingException?
52. Как «под капотом» работает Hibernate? Как бы вы написали собственный Hibernate?
Многопоточность
53. Каковы преимущества и недостатки использования многопоточности?
54. Расскажите о четырех способы работы со многими потоками и чем отличается wait … notify … notifyAll от synchronized? От Future?
55. Что такое и зачем нужен ThreadLocal?
56. В чем разница между Thread.sleep() и Thread.yield()?
57. Как работает Thread.join()?
58. Что такое deadlock?
59. Что такое race condition?
60. Для чего использовать volatile, synchronized, transient, native?
61. Расскажите о приоритетах потоков.
62. Что такое и для чего устанавливать потоки-демоны?
63. Почему не желательно использовать Thread.stop()?
64. Как реализовать пул потоков?
Collections
65. Чем отличается List от Set?
66. В чем разница между HashSet, LinkedHashSet и TreeSet?
67. Какова внутренняя структура HashMap?
68. Каково время поиска элемента в ArrayList, HashSet?
69. Как реализовать свой Stack?
70. Как работает метод put в HashMap? Почему нам нужно высчитывать позицию бакета? В чем преимущества такой операции?
71. Чем отличаются друг от друга HashMap и TreeMap? Когда и где их нужно использовать?
72. Каково внутреннее строение TreeMap? Рассказать о RBT.
Stream API
73. Какие есть методы в интерфейсе Stream?
74. Чем отличается метод map от flatMap?
75. Какой функциональный интерфейс использует метод filter?
Базы данных
76. В чем разница между реляционными и нереляционные базами данных?
77. Как хранятся соотношения one-to-one, one-to-many и many-to-many в виде таблиц?
78. Что такое нормализация БД? Приведите примеры из реального проекта.
79. Какие есть виды индексов в БД?
Практические задания
80. Valid parentheses (задача с LeetCode).
81. Reverse Linked List (задача с LeetCode).
82. Дана String s, найти длину максимального substring без повтора символов.
83. Определить, является ли односвязный LinkedList палиндромом.
Senior
Общие вопросы
1.Когда лучше использовать наследование, а не агрегацию?
2. Расскажите о принципах работы Kubernetes.
Java Core
3. В чем разница между Java NIO и Java IO?
4. Чем Lambda отличается от анонимного класса?
5. Расскажите о Java Memory Model.
6. Какие есть типы памяти в JVM?
7. Опишите жизненный цикл Java-объекта. Каким образом объект переходит из одной области памяти Garbage Collector в другую? Что является триггером такого перехода?
8. Каким образом можно заставить JVM запустить Garbage Collector?
9. Какие существуют Garbage Collectors в JVM и зачем их столько?
10. Какие виды Garbage Collector есть в HotSpot? Как работают?
11. Что будет с Garbage Collector, если finalize() будет долго выполняться или в процессе выполнения получим исключение?
12. Чем ForkJoinPool отличается от ScheduledThreadPoolExecutor и ThreadPoolExcutor?
13. В чем разница между HashMap, WeakHashMap, Hashtable, IdentityHashMap?
14. Что такое LinkedHashMap?
15. Что такое EnumSet? Для чего использовать? Как реализовать?
16. Расскажите об особенностях сериализации в Java. Зачем serialVersionUID и InvalidClassException?
17. В чем проблема сериализации Singleton?
18. Какие бывают алгоритмы обхода деревьев и почему они разные?
19. Что такое deadlock? Какие типы существуют? Нарисуйте схематично, как может произойти.
Базы данных
20. Что такое ACID?
21. Что означает CAP-теорема?
22. Какие есть уровни изоляции транзакций?
23. Есть ли смысл отказываться от использования ORM?
24. Что такое n+1 проблема?
25. Что такое cartesian product проблема?
Библиотеки и инструменты
26. Каким образом можно построить monitoring в Java? Расскажите об особенностях использования Java micrometrics или DropWizard, или Prometheus frameworks.
27. Опишите механизм работы ORM.
28. Какие способы выборки данных в Hibernate вы знаете?
29. Какие изоляции транзакций существуют в Hibernate?
Spring
30. Что такое IoC и DI?
31. Каков жизненный цикл объектов, которые создает Spring?
32. Какие есть виды контекстов?
33. Как создать и поднять контекст с целью тестирования приложения?
34. Какие возможности Spring предоставляет для коммуникации с базой данных?
35. Каковы признаки того, что класс — Java Bean? Чем POJO отличается от Java Bean?
36. Опишите механизм инъекции зависимости в Spring.
37. Почему все зависимости Spring являются Java Beans? Возможно ли использовать Spring для управления зависимостями между НЕ Java Beans классами?
38. Чем Spring singleton отличается от prototype?
39. Есть ли смысл отказываться от использования Dependency Injection?
Многопоточность
40. Что такое race-condition?
41. Какие элементы есть в java.util.concurrent пакете?
42. Что такое optimistic и pessimistic locking?
43. Каковы особенности многопоточности в Java EE и Spring?
Stream API
44. Каковы основные принципы Stream API?
Практические задания
45. Реализовать сервис, который на вход принимает url и возвращает короткую версию (вроде bit.ly/86gfr3).
коллекций Java — hashCode () и equals ()
пакет crunchify.com.tutorials;
/ **
*
* @author Crunchify.com
* Как переопределить метод equals () в Java?
* Как переопределить метод hasCode () в Java?
* версия: 1.2
*
* /
открытый класс CrunchifyImplementEqualsHashCode {
общедоступный статический void main (String [] args) {
CrunchifyImashplementCrunchifyImashplement
Crunchify one = новый Crunchify (1);
Crunchify two = новый Crunchify (1);
crunchifyTest.test1 (один, два);
Crunchify three = новый Crunchify (1);
Crunchify four = новый Crunchify (2);
crunchifyTest. test2 (три, четыре);
}
public void test1 (Crunchify one, Crunchify two) {
if (one.equals (two)) {
System.out.println («Test1: One и Two равны»);
} else {
System.out.println («Test1: один и два не равны»);
}
}
public void test2 (Crunchify три, Crunchify четыре) {
if (three.равно (четыре)) {
System.out.println («Test2: три и четыре равны»);
} else {
System.out.println («Test2: три и четыре не равны»);
}
}
}
class Crunchify {
private int value;
Crunchify (int val) {
value = val;
}
public int getValue () {
возвращаемое значение;
}
// Метод переопределяет или реализует метод, объявленный в супертипе.
@Override
public boolean equals (Object o) {
// null check
if (o == null) {
return false;
}
// этот экземпляр проверяет
if (this == o) {
return true;
}
// instanceof Проверка и проверка фактического значения
if ((o instanceof Crunchify) && (((Crunchify) o) . getValue () == this.value)) {
return true;
} else {
возврат false;
}
}
@Override
public int hashCode () {
int result = 0;
результат = (int) (значение / 11);
возврат результата;
}
}
Как правильно реализовать хэш-код Java
В SitePoint мы всегда стремимся расширить круг тем, которые мы рассматриваем.В последнее время мы нацелены на изучение мира Java. Если вы сильный Java-разработчик и хотите внести свой вклад в нашу репортаж, поделитесь несколькими идеями для статей, которые вы хотели бы написать.
Итак, вы решили, что вам не хватает идентичности, и написали красивую реализацию равно
?
Отлично! Но теперь вам нужно, чтобы также реализовал хэш-код
.
Давайте посмотрим, почему и как это делать правильно.
Равенство и хеш-код
В то время как равенство имеет смысл с общей точки зрения, хэш-коды гораздо более технические. Если бы мы были немного строги к ним, мы могли бы сказать, что они просто деталь реализации для повышения производительности.
В большинстве структур данных равно
, чтобы проверить, содержат ли они элемент. Например:
Список list = Arrays.asList («a», «b», «c»);
логическое значение contains = list.contains ("b");
Переменная содержит
— это истина
, потому что, хотя экземпляры "b"
не идентичны (опять же, игнорируя интернирование строки), они равны.
Однако сравнение каждого элемента с экземпляром, заданным для элемента , содержащего
, является расточительным, и целый класс структур данных использует более производительный подход. Вместо того, чтобы сравнивать запрошенный экземпляр с каждым элементом, который они содержат, они используют ярлык, который уменьшает количество потенциально равных экземпляров, а затем сравнивает только их.
Этот ярлык представляет собой хэш-код, который можно рассматривать как равенство объекта, сведенное к целочисленному значению. Экземпляры с одинаковым хеш-кодом не обязательно равны, но одинаковые экземпляры имеют одинаковый хеш-код.(Или должен был, мы обсудим это в ближайшее время.) Такие структуры данных часто называют в честь этого метода, который можно узнать по Hash
в их названии, причем HashMap
является наиболее заметным представителем.
Вот как они обычно работают:
- Когда элемент добавляется, его хэш-код используется для вычисления индекса во внутреннем массиве (называемом бакетом).
- Если другие, не равные элементы имеют одинаковый хэш-код, они попадают в одну и ту же корзину и должны быть объединены вместе, e.г. добавив их в список.
- Когда экземпляр передан на
, содержащий
, его хэш-код используется для вычисления корзины. Только элементы в нем сравниваются с экземпляром.
Таким образом, очень мало, в идеале не требуется равно
сравнений, необходимых для реализации содержит
.
Поскольку равно
, хэш-код
определен в объекте Object
.
Мысли о хешировании
Если hashCode
используется в качестве ярлыка для определения равенства, то действительно есть только одна вещь, о которой мы должны заботиться: равные объекты должны иметь один и тот же хэш-код.
Вот почему, если мы переопределим равным
, мы должны создать соответствующую реализацию hashCode
! В противном случае вещи, которые равны в соответствии с нашей реализацией, скорее всего, не будут иметь одинаковый хэш-код, потому что они используют реализацию объекта Object
.
Хэш-код
КонтрактЦитирование источника:
Общий контракт
hashCode
:
- Всякий раз, когда он вызывается для одного и того же объекта более одного раза во время выполнения Java-приложения, метод
hashCode
должен последовательно возвращать одно и то же целое число, при условии, что никакая информация, используемая в равных сравнениях для объекта, не изменяется. Это целое число не обязательно должно оставаться непротиворечивым от одного выполнения приложения к другому выполнению того же самого приложения.- Если два объекта равны в соответствии с методом
equals (Object)
, то вызов методаhashCode
для каждого из двух объектов должен привести к одинаковому целочисленному результату.- Не требуется, чтобы, если два объекта не равны согласно методу
equals (Object)
, то вызов методаhashCode
для каждого из двух объектов должен давать различные целочисленные результаты.Однако программист должен знать, что получение различных целочисленных результатов для неравных объектов может улучшить производительность хеш-таблиц.
Первая пуля отражает свойство согласованности , равное
, а второе — это требование, которое мы выдвинули выше. В третьем говорится о важной детали, которую мы обсудим чуть позже.
Реализация хэш-кода
Очень простая реализация Person. hashCode
выглядит следующим образом:
@Override
public int hashCode () {
вернуть объекты.хеш (firstName, lastName);
}
Хэш-код человека вычисляется путем вычисления хэш-кодов для соответствующих полей и их объединения. И то, и другое остается для Объекты
— функции полезности , хэш
.
Выбор полей
Но какие поля актуальны? Требования помогают ответить на этот вопрос: если одинаковые объекты должны иметь один и тот же хэш-код, тогда при вычислении хэш-кода не должно быть поля, которое не используется для проверки равенства. (В противном случае два объекта, которые отличаются только этими полями, будут равны, но имеют разные хэш-коды.)
Таким образом, набор полей, используемых для хеширования, должен быть подмножеством полей, используемых для равенства. По умолчанию оба будут использовать одни и те же поля, но следует учесть некоторые детали.
Консистенция
Во-первых, это требование согласованности. Его следует трактовать достаточно строго. Хотя он позволяет изменять хэш-код при изменении некоторых полей (что часто неизбежно для изменяемых классов), структуры данных хеширования не подготовлены для этого сценария.
Как мы видели выше, хэш-код используется для определения сегмента элемента.Но если поля, относящиеся к хешу, изменяются, хеш не пересчитывается и внутренний массив не обновляется.
Это означает, что последующий запрос с таким же объектом или даже с тем же самым экземпляром завершится ошибкой! Структура данных вычисляет текущий хэш-код, отличный от того, который используется для хранения экземпляра, и ищет не ту корзину.
Вывод: лучше не использовать изменяемые поля для вычисления хеш-кода!
Производительность
Хэш-коды могут в конечном итоге вычисляться примерно так же часто, как равно
.Это вполне может произойти в критических для производительности частях кода, поэтому имеет смысл подумать о производительности. И в отличие от равно
, есть немного больше возможностей для его оптимизации.
Если не используются сложные алгоритмы или не задействовано много-много полей, арифметические затраты на комбинирование их хэш-кодов столь же незначительны, как и неизбежны. Но следует учитывать, все ли поля нужно включать в вычисление! Особенно с подозрением следует относиться к коллекциям.Например, списки и наборы будут вычислять хэш для каждого из своих элементов. Вопрос о том, нужен ли их звонок, следует рассматривать в индивидуальном порядке.
Если производительность имеет решающее значение, использование Objects.hash
может быть не лучшим выбором, потому что для этого требуется создание массива для его переменных.
Но общее правило оптимизации остается в силе: не делайте этого преждевременно! Используйте общий алгоритм хэш-кода, возможно, откажитесь от включения коллекций и оптимизируйте только после того, как профилирование покажет потенциал для улучшения.
Столкновения
Что насчет этой реализации?
@Override
public int hashCode () {
возврат 0;
}
Это быстро, это точно. И одинаковые объекты будут иметь одинаковый хэш-код, поэтому мы тоже умеем это делать. В качестве бонуса не используются изменяемые поля!
Но помните, что мы говорили о ведрах? Таким образом, все экземпляры окажутся одинаковыми! Обычно в результате получается связанный список, содержащий все элементы, что ужасно для производительности.Каждый содержит
, например, запускает линейное сканирование списка.
Итак, мы хотим, чтобы в одной корзине было как можно меньше предметов! Алгоритм, который возвращает сильно различающиеся хэш-коды даже для очень похожих объектов, является хорошим началом.
Как добраться частично зависит от выбранных полей. Чем больше деталей мы включаем в вычисления, тем больше вероятность того, что хэш-коды будут отличаться. Обратите внимание, насколько это полностью противоположно нашим представлениям о производительности. Итак, что интересно, использование слишком большого количества полей или и слишком малого количества полей может привести к снижению производительности.
Другая часть предотвращения коллизий — это алгоритм, который используется для фактического вычисления хэша.
Вычисление хэша
Самый простой способ вычислить хэш-код поля — просто вызвать для него hashCode. Их можно было объединить вручную. Распространенный алгоритм — начать с некоторого произвольного числа и многократно умножить его на другое (часто маленькое простое число) перед добавлением хэша поля:
int простое число = 31;
int результат = 1;
результат = простое число * результат + ((firstName == null)? 0: firstName.хэш-код());
результат = простое число * результат + ((lastName == null)? 0: lastName.hashCode ());
вернуть результат;
Это может привести к переполнению, что не представляет особой проблемы, поскольку не вызывает исключений в Java.
Обратите внимание, что даже отличные алгоритмы хеширования могут привести к нехарактерно частым конфликтам, если входные данные имеют определенные шаблоны. В качестве простого примера предположим, что мы вычислим хэш точек, добавив их координаты x и y. Может показаться неплохим, пока мы не поймем, что часто имеем дело с точками на линии f (x) = -x
, что означает x + y == 0
для всех из них.Столкновения, в изобилии!
Но еще раз: используйте общий алгоритм и не беспокойтесь, пока профилирование не покажет, что что-то не так.
Резюме
Мы видели, что вычисление хэш-кодов похоже на сжатие равенства до целочисленного значения: равные объекты должны иметь один и тот же хэш-код, и по соображениям производительности лучше, если как можно меньше неравных объектов совместно используют один и тот же хэш.
Это означает, что hashCode
всегда должен быть переопределен, если равно
.
При реализации hashCode
:
- Используйте те же поля, которые используются в
, равняется
(или его подмножеству). - Лучше не включать изменяемые поля.
- Не следует вызывать
hashCode
для коллекций. - Используйте общий алгоритм, если шаблоны во входных данных им не противодействуют.
Помните, что хэш-код
связан с производительностью, поэтому не тратьте слишком много энергии, если профилирование не указывает на необходимость.
Item 09 — Всегда переопределять hashCode при переопределении равно
С
Эффективная Java 2 / e на Джошуа БлохВы должны переопределить hashCode в каждом классе, который переопределяет значение
Ключевое положение, которое нарушается, когда вы не можете переопределить hashCode
- Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен давать одинаковый целочисленный результат.
Простой рецепт
- Сохранение некоторого постоянного ненулевого значения, например 17, в переменной типа int с именем result. (f >>> 32)).
- Если поле является плавающим, вычислить Float.floatToIntBits (f).
- Если поле является двойным, вычислите Double.doubleToLongBits (f), а затем хешируйте полученную длину, как на шаге 2.1.3.
- Если поле является ссылкой на объект и метод equals этого класса сравнивает поле, рекурсивно вызывая равенства, рекурсивно вызывает hashCode для поля. Если требуется более сложное сравнение, вычислите «каноническое представление» для этого поля и вызовите hashCode для канонического представления.Если значение поля равно нулю, верните 0 (или другую константу, но обычно 0).
- Если поле является массивом, относитесь к нему так, как если бы каждый элемент был отдельным полем. То есть вычислить хэш-код для каждого значимого элемента, рекурсивно применяя эти правила, и объединить эти значения на шаге 2.b. Если каждый элемент в поле массива имеет значение, вы можете использовать один из методов Arrays.hashCode, добавленных в версии 1.5.
// Лениво инициализированный, кешированный хэш-код частный изменчивый int hashCode; // (См. Пункт 71) @Override public int hashCode () { int result = hashCode; if (result == 0) { результат = 17; результат = 31 * результат + код площади; результат = 31 * результат + префикс; результат = 31 * результат + номер строки; hashCode = результат; } вернуть результат; }
Не поддавайтесь искушению исключить значительные части объекта из вычисления хэш-кода для повышения производительности
Автор:
The Finest ArtistПроблема в методах equals () и hashCode () в java.
— общий Прежде всего, ваш код будет работать, и вы сможете выполнять операции HashSet
без реализации hashCode ()
или equals ()
. Это потому, что все классы являются производными от класса Object
, а класс Object
имеет реализации equals ()
и hashCode ()
, которые наследуются всеми подклассами, включая ваш класс Student
. Перейдите по ссылкам, чтобы узнать больше о том, каким правилам должны следовать методы и реализациям по умолчанию.
Назначение функции equals ()
для проверки равенства. В настоящее время ваша функция equals ()
предполагает, что два ученика
равны, если длины их имен равны. Это необычное предположение, которого вы, вероятно, не планировали. Поскольку единственная особенность Student
— это его имя, лучше предположить, что имена уникальны, и вы должны проверить, совпадают ли их имена. Если несколько номеров Student
могут иметь одно и то же имя, вы должны придумать дополнительные функции, чтобы различать каждого учащегося.
Назначение функции hashCode ()
— вернуть целочисленное значение на основе свойств объекта, чтобы отличить его от других объектов. Два объекта, которые сравнивают равные с помощью метода equals ()
, должны возвращать один и тот же хэш-код. Однако два неравных объекта могут возвращать один и тот же хеш-код, хотя чем меньше это происходит, тем лучше хэш-функция. Так что хеш-код не является гарантией равенства.
Давайте кратко обсудим, как работает HashSet
.
Когда вы добавляете объект Student
s
в HashSet
, HashSet
вызовет s.hashCode ()
, чтобы получить его хеш-значение, а затем использовать это значение для хранения объекта в некоторой корзине. соответствующий хэш-коду.
Когда вы пытаетесь найти Student
s
в HashSet
, HashSet
вызовет s. hashCode ()
и заглянет в соответствующую корзину. В корзине может быть несколько Student
, которые соответствуют хэш-коду, но HashSet
будет искать Student
t
, который также соответствует s
через проверку равно
.
Теперь вернемся к поведению вашего кода.
1. Прежде всего вам нужно изменить метод equals ()
, так как в настоящее время он оценивает Student
с именем «uvw»
и другой с «xyz»
как равные.
2. Если вы правильно реализуете equals ()
, но не укажете hashCode ()
, то hashCode () больше не будет полагаться на свойства объекта. Это нарушит правило «Два объекта, которые сравниваются с равными значениями с помощью метода
equals ()
, должны возвращать один и тот же хэш-код» . HashSet
поместит объект в какое-то ведро. Теперь, если вы ищете Student
в наборе, набор, скорее всего, не найдет совпадения, даже если в наборе существует такой же Student
, потому что он, вероятно, не будет искать в том же сегменте. Пример случая.
Итак, суть в том, что вы должны реализовать equals ()
и hashCode ()
, которые согласованы друг с другом и соответствуют спецификациям, предоставленным языком Java, в противном случае структуры, зависящие от этих методов, выйдут из строя.Надеюсь, объяснение было ясным
Эффективная Java с Groovy - Правильно ли вы реализуете equals и hashCode?
Рассмотрим следующий код Groovy.
класс Продукт {
Строка sku
Описание строки
Цена BigDecimal
}
Теперь давайте применим класс Product
следующим образом.
Product book1 = новый продукт (артикул: 'P101', описание: 'Effective Java', цена: 599.0)
Product book2 = новый продукт (артикул: 'P101', описание: 'Effective Java', цена: 599.0)
println book1 == book2
Мы создаем два экземпляра Products
, используя один и тот же набор значений. Приведенный выше фрагмент кода печатает false
. Поскольку мы не переопределяли значение равным
, метод, определенный в java.lang.Object
, будет унаследован классом Product
. Согласно реализации в java.lang.Object
, два экземпляра равны только тогда, когда обе ссылки указывают на один и тот же объект.
// Продолжение предыдущего примера
def stock = [:]
запас [book1] = 100
распечатанные материалы [book2]
Здесь я создаю объект Map ( [:]
создает пустую карту) и сохраняю значение 100 с объектом book1 в качестве ключа.Затем я использовал второй объект книги для поиска. Здесь мы получаем null. Значение, возвращаемое методом hashCode ()
, используется в операциях карты. Согласно реализации из java.lang.Object
, каждый экземпляр будет иметь уникальный хэш-код.
Теперь мы понимаем, что мы должны переопределить методы equals ()
и hashCode ()
в классе Product
. При их переопределении нам также необходимо убедиться, что всякий раз, когда два экземпляра равны, они также должны иметь одинаковый хэш-код.
Ниже приводится пример реализации.
класс Продукт {
Строка sku
Описание строки
Цена BigDecimal
boolean equals (o) {
если (this.is (o)) вернуть истину
если (getClass ()! = o.class) вернуть ложь
Продукт product = (Продукт) o
if (description! = product.description) вернет false
if (price! = product.price) вернуть false
if (sku! = product.sku) вернуть false
вернуть истину
}
int hashCode () {
int результат
результат = (sku! = null? sku.hashCode (): 0)
результат = 31 * результат + (описание! = null? description.hashCode (): 0)
результат = 31 * результат + (цена! = null? price.hashCode (): 0)
вернуть результат
}
}
Здесь я использовал шаблон IntelliJ IDEA, предоставленный моей IDE. Можно также использовать служебные классы, предоставляемые библиотеками, такими как Apache Commons Lang (он дает EqualsBuilder
и HashCodeBuilder
). Я обнаружил две проблемы с описанным выше подходом.
Первый - чтобы поддерживать согласованность между равным
и hashCode
, оба эти метода должны полагаться на один и тот же набор атрибутов.Следовательно, мы нарушаем принцип DRY (Don't Repeat Yourself) в приведенном выше коде. Обратите внимание, что равно
, а хэш-код
сам по себе решает, какие атрибуты им следует учитывать.
Секунда - Часто наш код продолжает развиваться, и мы могли бы добавлять больше полей в наш класс Product
. Хотя генерация кода IDE работает впервые, добавление еще одного атрибута не помогает. Разработчики также могут забыть обновить эти методы, когда они вводят новые атрибуты.
Если вы новичок в Groovy, возможно, вы захотите познакомиться с Groovy из этого сообщения.
Теперь давайте исследуем, как Groovy решает две указанные выше проблемы. Groovy предоставляет преобразование AST groovy.transform. EqualsAndHashCode
, которое можно применить к классу следующим образом.
импорт groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Product {
Строка sku
Описание строки
Цена BigDecimal
}
Во время компиляции Groovy сгенерирует реализации как для , равного
, так и для hashCode
, используя все три определенных атрибута.Вы можете настроить, если необходимо учитывать только определенные поля для реализации равняется
и hashCode
. Пример следующий.
@EqualsAndHashCode (включает = "артикул, описание, цена")
Учитываемые атрибуты определены в одном месте. Поэтому мы придерживаемся принципа DRY. Кроме того, если вы добавите дополнительные атрибуты, вам придется скомпилировать свой код, и методы равны
и hashCode
методы будут восстановлены во время компиляции.Таким образом, Groovy решает обе указанные выше проблемы.
Если вам интересно узнать больше о том, как Effective Java применяется к коду Groovy, вы можете взглянуть на мою презентацию GR8Conf EU.
Ссылки:
Мастер создания equals () и hashCode () - IntelliJ IDEA
Используйте этот мастер для создания методов equals () и hashCode ().
Элемент | Описание |
---|---|
Страница1 | |
Шаблон | Используйте этот раскрывающийся список для выбора предопределенного шаблона скорости или щелкните, чтобы открыть диалоговое окно «Шаблоны». |
Принять подклассы в качестве параметра для метода equals () | Хотя обычно это несовместимо со спецификацией Object.equals (), принятие подклассов может быть необходимо для корректной работы сгенерированного метода с фреймворками, которые генерируют подклассы Proxy, например Hibernate. |
Использовать геттеры во время генерации кода | Если этот флажок установлен, геттеры используются в equals () вместо прямого доступа к полям: getField () vs field .Щелкните Далее, чтобы открыть следующую страницу. |
Page 2 | |
Выберите поля для включения в equals () | Выберите поля, которые следует использовать для определения равенства. Каждое из значений выбранного поля будет сравниваться, и объекты будут считаться равными только в том случае, если все указанные здесь значения полей эквивалентны. Щелкните Далее, чтобы открыть следующую страницу. |
Page 3 | |
Выберите поля для включения в hashCode () | Выберите поля для генерации хэш-кода.Обратите внимание, что только поля, которые были включены в метод equals (), могут участвовать в создании хэш-кода. Все эти поля выбраны по умолчанию, но вы можете отменить их выбор при необходимости. Щелкните Далее, чтобы открыть следующую страницу. |
Page 4 | |
Выбрать все ненулевые поля | Эта страница появляется, если какое-либо из выбранных полей имеет непримитивный тип, чтобы избежать ненужных проверок. Другими словами, если установлен флажок для любого из этих полей, предполагается, что такое поле никогда не имеет нулевого значения, и такая проверка не будет включена в сгенерированные методы. Нажмите «Готово», чтобы завершить работу мастера и создать методы eqauls () и hashCode (). |
Последнее изменение: 19 августа 2020 г.
@EqualsAndHashCode
Equality made easy: генерирует
hashCode
и равно
реализациям из полей вашего объекта.Обзор
Любое определение класса может быть аннотировано с помощью @EqualsAndHashCode
, чтобы позволить lombok создавать реализации методов equals (Object other)
и hashCode ()
.По умолчанию он будет использовать все нестатические, непереходные поля, но вы можете изменить используемые поля (и даже указать, что должен использоваться вывод различных методов), пометив элементы типа с помощью @ EqualsAndHashCode. Include
или @ EqualsAndHashCode. Исключить
. В качестве альтернативы вы можете указать, какие именно поля или методы вы хотите использовать, пометив их с помощью @ EqualsAndHashCode.Include
и используя @EqualsAndHashCode (onlyExplicitlyIncluded = true)
.
Если применить @EqualsAndHashCode
к классу, который расширяет другой, эта функция становится немного сложнее. Обычно автоматическое создание метода , равного
и hashCode
для таких классов - плохая идея, поскольку суперкласс также определяет поля, которые также нуждаются в коде equals / hashCode, но этот код не будет сгенерирован. Установив для callSuper
значение true , вы можете включить методы equals
и hashCode
вашего суперкласса в сгенерированные методы.Для хэш-кода
результат super.hashCode ()
включается в алгоритм хеширования, а для равно
сгенерированный метод вернет false, если супер-реализация считает, что он не равен переданному объекту. Имейте в виду, что не все реализации равно
правильно справляются с этой ситуацией. Однако сгенерированный ломбоком равен
, реализации действительно ли обрабатывают эту ситуацию должным образом, поэтому вы можете безопасно назвать свой суперкласс равным, если он также имеет сгенерированный ломбоком метод равно
.Если у вас есть явный суперкласс, вы вынуждены указать значение для callSuper
, чтобы подтвердить, что вы его рассмотрели; невыполнение этого правила приводит к предупреждению.
Установка callSuper
на true , когда вы ничего не расширяете (вы расширяете java.lang.Object
), является ошибкой времени компиляции, потому что это превратит сгенерированные equals ()
и hashCode ()
реализации, чтобы они имели такое же поведение, как простое наследование этих методов от java.lang.Object
: только один и тот же объект будет равен друг другу и будет иметь одинаковый хэш-код. Отсутствие установки callSuper
на true при расширении другого класса генерирует предупреждение, потому что, если суперкласс не имеет (важных для равенства) полей, lombok не может сгенерировать для вас реализацию, которая учитывает поля, объявленные вашими суперклассами. Вам нужно будет написать свои собственные реализации или положиться на средство связывания callSuper
. Также можно использовать ломбок .equalsAndHashCode.callSuper
ключ конфигурации.
НОВОЕ в Lombok 0.10: Если ваш класс - final
и не расширяет java.lang.Object
, lombok генерирует метод canEqual
, который означает, что прокси JPA все еще могут быть равны их базовому классу, но подклассы, которые добавляют новое состояние не нарушайте договор равных. Сложные причины, по которым такой метод необходим, объясняются в этой статье: Как написать метод равенства на Java. Если все классы в иерархии представляют собой смесь классов scala case и классов с методами равенства, созданными lombok, все равенство будет «просто работать». Если вам нужно написать свои собственные методы равенства, вы всегда должны переопределять canEqual
, если вы меняете на
и hashCode
.
НОВИНКА в Lombok 1.14.0: . Чтобы добавить аннотации к , другой параметр
метода равен
(и, при необходимости, canEqual
), можно использовать onParam = @ __ ({@ AnnotationsHere})
. Но будьте осторожны! Это экспериментальная функция. Дополнительные сведения см. В документации по функции onX.
НОВОЕ в Lombok 1.18.16: Результат сгенерированного hashCode ()
можно кэшировать, установив для cacheStrategy
значение, отличное от CacheStrategy.NEVER
. Не используйте , если объекты аннотированного класса могут быть изменены любым способом, который приведет к изменению результата hashCode ()
.
С Lombok
import lombok. EqualsAndHashCode; |
Ванильная Ява
import java. util.Arrays; |
Поддерживаемые ключи конфигурации:
-
ломбок.equalsAndHashCode.doNotUseGetters
= [true
|false
] (по умолчанию: false) - Если установлено значение
true
, lombok будет обращаться к полям напрямую вместо использования геттеров (если они есть) при генерации методовравно
иhashCode
. Параметр аннотации «doNotUseGetters
», если он явно указан, имеет приоритет над этим параметром. -
lombok.equalsAndHashCode.callSuper
= [вызов
|skip
|предупреждать
] (по умолчанию: предупреждать) - Если установлено значение
call
, lombok будет генерировать вызовы реализации суперклассаhashCode
, абудет равно
, если ваш класс что-то расширяет.Если установлено значение, пропускать
такие вызовы не создаются. Поведение по умолчанию похоже напропустить
с дополнительным предупреждением. -
lombok.equalsAndHashCode.flagUsage
= [предупреждение
|error
] (по умолчанию: не установлено) - Lombok будет отмечать любое использование
@EqualsAndHashCode
как предупреждение или ошибку, если настроено.
Мелкий шрифт
Массивы «глубоко» сравниваются / хэш-кодируются, что означает, что массивы, которые содержат сами себя, приведут к StackOverflowError
s. Однако это поведение не отличается от, например, Список массивов
.
Вы можете с уверенностью предположить, что используемая реализация hashCode не будет меняться между версиями lombok, однако эта гарантия не высечена; если от использования альтернативного алгоритма хеширования можно добиться значительного повышения производительности, он будет заменен в будущей версии.
В целях равенства 2 NaN
(не число) значений для чисел с плавающей запятой и двойных чисел считаются равными, даже если «NaN == NaN» вернет false.Это аналогично методу equals java.lang.Double
и фактически требуется, чтобы гарантировать, что сравнение объекта с его точной копией возвращает true
для равенства.
Если существует , любой метод с именем hashCode
или равен
, независимо от типа возвращаемого значения, никакие методы не будут сгенерированы, а вместо этого выдается предупреждение. Эти 2 метода должны быть синхронизированы друг с другом, что ломбок не может гарантировать, если он не сгенерирует все методы, поэтому вы всегда получаете предупреждение, если один или оба метода уже существуют. Вы можете пометить любой метод с помощью @ lombok.experimental. Терпеть
, чтобы скрыть их от ломбока.
Попытка исключить поля, которые не существуют или были бы исключены в любом случае (поскольку они статические или временные), приводит к появлению предупреждений в названных полях.
Если метод отмечен для включения и имеет то же имя, что и поле, он заменяет поле (метод включен, поле исключается).
До версии lombok 1.16.22 включение / исключение могло быть выполнено с помощью параметров из
и exclude
аннотации @EqualsAndHashCode
.Этот старый механизм включения все еще поддерживается, но в будущем он будет исключен.
По умолчанию любые переменные, начинающиеся с символа $, автоматически исключаются. Вы можете включить их, только пометив их с помощью @ EqualsAndHashCode.Include
.
Если для включаемого поля существует метод получения, он вызывается вместо использования прямой ссылки на поле.