JavaRush /Java блог /Random UA /Java Core. Запитання до співбесіди, ч. 3
Vadim625
27 рівень

Java Core. Запитання до співбесіди, ч. 3

Стаття з групи Random UA
У попередніх двох статтях ми обговорабо деякі важливі питання, які Вам найчастіше ставлять на співбесідах. Настав час продовжити та розглянути решту питань.
Java Core.  Запитання до співбесіди, ч. 3 - 1

Глибоке копіювання та поверхневе копіювання

Точною копією оригіналу є його клон. Java це означає можливість створювати об'єкт з аналогічною структурою, як і у вихідного об'єкта. Метод clone()забезпечує цю функціональність. Поверхне копіювання копіює настільки малу частину інформації, наскільки це можливо. За умовчанням, клонування Java є поверховим, тобто. Object classне знає про структуру класу, яку він копіює. При клонуванні, JVM робить такі речі:
  1. Якщо клас має лише члени примітивних типів, то буде створено абсолютно нову копію об'єкта та повернуто посилання на цей об'єкт.
  2. Якщо клас містить як члени примітивних типів, а й іншого типу класу, тоді копіюються посилання об'єкти цих классов. Отже, обидва об'єкти матимуть однакові посилання.
Глибоке копіювання дублює все. Глибоке копіювання – це дві колекції, до однієї з яких дублюються всі елементи оригінальної колекції. Ми хочемо зробити копію, при якій внесення змін до будь-якого елемента копії не торкнеться оригінальної колекції. Глибоке клонування вимагає виконання таких правил:
  1. Немає потреби копіювати окремо примітивні дані;
  2. Усі класи-члени в оригінальному класі мають підтримувати клонування. Для кожного члена класу повинен викликатись super.clone()при перевизначенні методу clone();
  3. Якщо якийсь член класу не підтримує клонування, то методі клонування необхідно створити новий екземпляр цього класу і скопіювати кожен його член з усіма атрибутами в новий об'єкт класу, по одному.
Дізнайтесь більше про клонування тут

Що таке синхронізація? Блокування на рівні об'єкта та блокування на рівні класу?

Синхронізація відноситься до багатопоточності. Синхронізований блок коду може виконуватися одночасно лише одним потоком. Java дозволяє обробляти одночасно кілька потоків. Це може призвести до того, що два або більше потоку хочуть отримати доступ до одного поля. Синхронізація дозволяє уникнути помилок пам'яті, які виникають у разі неправильного використання ресурсів пам'яті. Коли метод оголошений як синхронізований, нитка утримує монітор. Якщо інша нитка намагається в цей час отримати доступ до синхронізованого методу, то потік блокується, і чекає на звільнення монітора. Синхронізація в Java здійснюється спеціальним ключовим словом synchronized. Ви можете помічати таким чином окремі блоки чи методи у вашому класі. Ключове слово synchronized не може бути використане разом із змінними чи атрибутами класу. Блокування на рівні об'єкта – механізм, коли ви хочете синхронізувати non-static метод чи non-static блок коду таким чином, що тільки один потік зможе виконати блок коду в даному екземплярі класу. Це потрібно завжди робити, щоб зробити екземпляр класу потокобезпечним. Блокування на рівні класузапобігає входу кількох потоків у синхронізований блок для всіх доступних екземплярів класу. Наприклад, якщо є 100 екземплярів класу DemoClass, то лише 1 потік зможе виконати demoMethod() використовуючи одну із змінних у певний момент часу. Це має бути завжди зроблено, щоб забезпечити безпеку статичного потоку. Дізнайтеся більше про синхронізацію тут.

Яка різниця між sleep() та wait()?

Sleep()це метод, який використовується, щоб затримати процес на кілька секунд. У випадку з wait(), нитка знаходиться в стані очікування, поки ми не викличемо метод notify()або notifyAll(). Основна відмінність полягає в тому, що wait()знімає блокування монітора, тоді як sleep()не звільняє блокування. Wait()використовується для багатострумових додатків, sleep()використовують просто для паузи виконання нитки. Thread.sleep()ставить поточний потік в "Not Runnable" стан на певну кількість часу. Нитка зберігає стан монітора, який був до виклику даного методу. Якщо ж інша нитка викликає t.interrupt(), нитка яка "заснула" - прокинеться. Зверніть увагу, щоsleep()є статичним методом, що означає, що він завжди впливає на поточний потік (той, що виконує метод sleep()). Поширеною помилкою є виклик t.sleep(), де tє іншим потоком; навіть тоді, коли поточна нитка, яка викликала метод sleep(), не є tниткою. Object.wait()посилає поточний потік в "Not Runnable" стан на деякий час, так само як і sleep(), але все ж таки з деяким нюансом. Wait()викликається для об'єкта, а чи не для нитки; ми називаємо цей об'єкт “lock object”. Перед викликом lock.wait()поточна нитка має бути синхронізована з “lock object”;wait()після цього знімає це блокування і додає нитку в ”wait list” пов'язаний з цим блокуванням. Пізніше інша нитка може бути синхронізована з тим самим lock object і викликати метод lock.notify(). Цей метод «розбудить» оригінальну нитку, яка все ще чекає. У принципі, wait()/ notify()можна порівняти з sleep()/ interrupt(), тільки активної нитки не потрібен прямий покажчик на нитку, що спить, потрібно тільки знати загальний lock object. Читайте детальну різницю тут.

Чи можна присвоїти null до цієї посилальної змінної?

Ні, не можна. У Java ліва частина оператора присвоювання має бути змінною. This - це спеціальне ключове слово, яке завжди дає поточний екземпляр класу. Це не будь-яка змінна. Так само, null не можна привласнити змінної, використовуючи ключове слово “super” або будь-яке інше.

Яка різниця між && &?

&- Побітова а &&- логічно.
  1. &оцінює обидві сторони операції;
  2. &&оцінює ліву сторону операції. Якщо вона true, він продовжує оцінити праву сторону.
Подивіться тут для глибшого розуміння.

Як перевизначити equals() та hachCode() методи?

hashCode()та equals()методи визначені в класі Object, який є батьківським класом для об'єктів Java. З цієї причини, всі об'єкти Java успадковують реалізацію за промовчанням для методів. Метод hashCode()використовується для отримання унікального цілого числа цього об'єкта. Це ціле число використовується для визначення розташування об'єкта, коли цей об'єкт необхідно зберегти, наприклад HashTable. За замовчуванням hashCode()повертає integerподання адресаи осередку пам'яті, де зберігається об'єкт. Метод equls(), як і випливає з його імені, використовується, щоб просто перевірити рівність двох об'єктів. Реалізація за замовчуванням перевіряє посилання на об'єкти щодо їх рівності. Нижче наведено важливі рекомендації для перезавантаження цих методів:
  1. Завжди використовуйте однакові атрибути об'єктів при генерації hashCode()та equals();
  2. Симетричність. Тобто. якщо для будь-яких об'єктів xі y x.equals(y)повертає true, то й y.equals(x)має повертати true;
  3. Рефлексивність. Для будь-якого об'єкта x x.equals(x)має повертати true;
  4. Постійність. Для будь-яких об'єктів xі y x.equals(y)повертає те саме, якщо інформація, використовувана у порівняннях, не змінюється;
  5. Транзитивність. Для будь-яких об'єктів x, yі zякщо x.equals(y)поверне true і y.equals(z)поверне true, то і x.equals(z)повинен повернути true;
  6. Щоразу, коли метод викликається в одного й того ж об'єкта під час виконання програми, він повинен повертати те саме число, якщо використовувана інформація не змінюється. hashCodeможе повертати різні значення для ідентичних об'єктів у різних екземплярах програми;
  7. Якщо два об'єкти рівні, відповідно до equals, то їх hashCodeповинні повертати однакові значення;
  8. Зворотна вимога необов'язкова. Два нерівні об'єкти можуть повертати однаковий hashCode. Однак для підвищення продуктивності краще, щоб різні об'єкти повертали різні коди.
Цікаві факти про ці методи читайте тут.

Розкажіть про модифікатори доступу

Класи Java, поля, конструктори та методи можуть мати один з чотирьох різних модифікаторів доступу: private Якщо метод або змінна позначені як private , то тільки код усередині одного класу може отримати доступ до змінної, або викликати метод. Код усередині підкласів не може отримати доступ до змінної або методу, так само як і не може отримати доступ із будь-якого іншого класу. Модифікатор private доступу найчастіше використовується для конструкторів, методів і змінних. default Модифікатор доступу defaultоголошується у тому випадку, якщо модифікатор не вказано взагалі. Даний модифікатор означає, що доступ до полів, конструкторів та методів даного класу може отримати код усередині самого класу, код усередині класів у тому самому пакеті. Підкласи не можуть отримати доступ до методів і змінних - членів суперкласу, якщо вони оголошені як default , якщо підклас не знаходиться в тому пакеті, що і суперклас. protected Модифікатор protected працює так само, як і default , за винятком того, що підкласи так само можуть отримати доступ до захищених методів та змінних суперкласу. Це твердження є правильним, навіть якщо підклас не знаходиться в тому ж пакеті, що і суперклас. public Модифікатор доступуpublic означає, що весь код може отримати доступ до класу, його змінних, конструкторів або методів незалежно від того, де розташований цей код. Java Core.  Запитання до співбесіди, ч. 3 - 2

Що таке збирач сміття? Чи можемо ми викликати його?

Збір сміття є функцією автоматичного керування пам'яттю у багатьох сучасних мовах програмування, таких як Java та мови в NET.Framework. Мови, які використовують збирання сміття, часто інтерпретують збирання сміття у віртуальній машині, такій як JVM. Збір сміття має дві мети: будь-яка невикористана пам'яті повинна бути звільнена, і пам'ять не повинна бути звільнена, якщо програма ще її використовуватиме. Чи можете ви запустити збирання сміття вручну? Ні, System.gc()надає вам такий великий доступ, як можна. Найкращим варіантом є виклик способу System.gc(), який натякне збирачеві сміття про необхідність запуску. Немає жодного способу запустити його негайно, оскільки збирач сміття є недетермінованим. Крім того, згідно з документацією,OutOfMemoryErrorне буде прокинуто, якщо віртуальній машині не вдалося звільнити пам'ять після повного складання сміття. Дізнайтесь більше про збирача сміття тут.

Що означає ключове слово native? Поясніть у деталях

Ключове слово native застосовується, щоб вказати, що метод реалізований над файлі Java, але іншою мовою програмування. Нативні методи використовувалися в минулому. У поточних версіях Java це потрібно рідше. В даний час природні методи необхідні, коли:
  1. Ви повинні викликати бібліотеку з Java, яка написана іншою мовою.
  2. Вам потрібен доступ до системних або апаратних ресурсів, до яких можна отримати доступ, використовуючи іншу мову (як правило, С). Насправді, багато системних функцій, які взаємодіють з реальним комп'ютером (наприклад диски або мережеві дані) можуть бути викликані лише нативним методом.
Недоліки використання бібліотек нативних методів теж значні:
  1. JNI/JNA можуть дестабілізувати JVM, особливо якщо ви спробуєте зробити щось складне. Якщо ваш природний метод робить щось неправильно, є ймовірність аварійного завершення JVM. Також, неприємні речі можуть статися, якщо ваш природний метод викликається з декількох ниток. І так далі.
  2. Програму з native кодом складніше дебажити.
  3. Native код вимагає окремої побудови фреймворків, що може створити проблеми з перенесенням на інші платформи.

Що таке серіалізація?

У комп'ютерних науках, у тих зберігання і передачі, серіалізація – це процес перекладу структури даних чи стану об'єкта у формат, який може бути збережений і відновлений потім у інший комп'ютерної середовищі. Після прийому серії бітів вони перераховуються відповідно до формату серіалізації, і можуть бути використані для створення семантично ідентичного клону вихідного об'єкта. Java надає автоматичну серіалізацію, яка вимагає, щоб об'єкт реалізував інтерфейсjava.io.Serializable. Реалізація інтерфейсу позначає клас як «серіалізується». В інтерфейсі java.io.Serializable немає методів серіалізації, але клас, що серіалізується, може додатково визначити методи, які будуть викликані як частина процесу серіалізації/дисеріалізації. При внесенні змін до класів необхідно враховувати, які з них будуть сумісні та не сумісні із серіалізацією. Ви можете прочитати повну інструкцію тут. Найголовніші пункти я наведу: Несумісні зміни:
  1. Видалення поля;
  2. Переміщення класу вгору чи вниз ієрархією;
  3. Зміна non-static поля на static або non-transient на transient;
  4. Зміна оголошеного типу примітивних даних;
  5. Зміна методу WriteObjectабо ReadObjectтак, що вони більше не пишуть або не читають стандартні поля;
  6. Зміна класу Serializableна Externalizableчи навпаки;
  7. Зміна класу enum на non-enum чи навпаки;
  8. Видалення Serializableабо Externalizable;
  9. Додавання writeReplaceчи readResolveметоду до класу.
Сумісні зміни:
  1. Додавання полів;
  2. Додавання/видалення класів;
  3. Додавання методів WriteObject/ReadObject[методи defaultReadObjectабо defaultWriteObjectповинні бути викликані на початку];
  4. Видалення методів WriteObject/ReadObject;
  5. Додавання java.io.Serializable;
  6. Зміна доступу до поля;
  7. Зміна static поля на non-static або transient на non-transient .
Посилання до попередніх частин: Java Core. Запитання до співбесіди, ч. 1 Java Core. Запитання до співбесіди, ч. 2. Оригінал статті Щасливого навчання!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ