Содержание

Ответы на вопросы на собеседование Java core (часть 2).

  • Возможно ли при переопределении (override) метода изменить: 
    1. Модификатор доступа
    2. Возвращаемый тип
    3. Тип аргумента или количество
    4. Имя аргументов
    5. Изменять порядок, количество или вовсе убрать секцию throws?
  1. Да, если расширять (package -> protected -> public)
  2. Да, если выполняется Downcasting(понижающее преобразование, преобразование вниз по иерархии) то есть возвращаемый тип в переопределенном методе класса наследника должен быть НЕ шире чем в классе родителе (Object -> Number -> Integer)
  3. Нет, в таком случае происходит Overload(перегрузка)
  4. Да
  5. Возможно изменять порядок. Возможно вовсе убрать секцию 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.

Методы класса 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().
  1. Используйте оператор == что бы проверить ссылку на объект, переданную в метод equals. Если ссылки совпадают — вернуть true. Это не обязательно, нужно скорее для оптимизации, но может сэкономить время в случае «тяжёлых» сравнений.
  2. Используйте оператор instanceof для проверки типа аргумента. Если типы не совпадают, вернуть false. 
  3. Преобразуйте аргумент к корректному типу. Так как на предыдущем шаге мы выполнили проверку, преобразование корректно.
  4. Пройтись по всем значимым полям объектов и сравнить их друг с другом. Если все поля равны — вернуть true. Для сравнения простых типов использовать ==. Для полей со ссылкой на объекты использовать equals. float преобразовывать в int  с помощью Float.floatToIntBits и сравнить с помощью ==. double преобразовывать в long  с помощью Double.doubleToLongBits и сравнить с помощью ==. Для коллекций вышеперечисленные правила применяются к каждому элементу коллекции. Нужно учитывать возможность null полей/объектов. Очерёдность сравнения полей может существенно влиять на производительность.
  5. Закончив реализацию 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?
Реализация метода 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 для чего нужен – Тарифы на сотовую связь