JavaRush /Java блог /Random UA /Розбір запитань та відповідей із співбесід на Java-розроб...
Константин
36 рівень

Розбір запитань та відповідей із співбесід на Java-розробника. Частина 15

Стаття з групи Random UA
Привіт привіт! Як багато потрібно знати Java розробнику? Можна довго сперечатися з цього питання, але правда в тому, що на співбесіді вас ганятимуть з теорії на повне зростання. Навіть за тими областями знань, які вам не доведеться скористатися в роботі. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 - 1Ну а якщо ви новачок, за вашими теоретичними знаннями пройдуть дуже серйозно. Якщо досвіду і великих досягнень ще немає, залишається лише перевірити міцність бази знань. Сьогодні ми продовжимо займатися зміцненням цієї бази, розбираючи найпопулярніші питання на співбесідах для Java-розробників. Полетіли!

Java Core

9. У чому різниця між статичним та динамічним зв'язуванням у Java?

На це питання я вже відповів у цій статті у 18 питанні про статичний та динамічний поліморфізм, раджу ознайомитись.

10. Чи можна використовувати private чи protected змінні в interface?

Ні, не можна. Так як коли ви оголошує інтерфейс, компілятор Java автоматично додає ключові слова public і abstract перед методами інтерфейсу та ключові слова public , static та final перед членами даних. Власне, якщо ви додасте private або protected , виникне конфлікт, і компілятор лаятиметься на модифікатор доступу повідомленням: "Modifier '<вибраний модифікатор>' not allowed here" Чому ж компілятор додає public , static і final змінним в інтерфейсі? Давайте розберемося:
  • public – інтерфейс надає можливість клієнту взаємодіяти з об'єктом. Якби змінні не були загальнодоступними, клієнти не мали б до них доступу.
  • static — інтерфейси неможливо створити (а точніше, їх об'єкти), тому змінна статична.
  • final — так як інтерфейс використовується для досягнення 100% абстракції, змінна має кінцевий вигляд (і не буде змінена).

11. Що таке Classloader і навіщо використовується?

Classloader або Завантажувач класів забезпечує завантаження класів Java. Точніше, забезпечують завантаження його спадкоємці — конкретні завантажувачі класів, т.к. сам ClassLoader абстрактний. Щоразу, коли завантажується будь-який .class-файл, наприклад, після звернення до конструктора або статичного методу відповідного класу, цю дію виконує один із спадкоємців класу ClassLoader . Є три види спадкоємців:
  1. Bootstrap ClassLoader - базовий завантажувач, реалізований на рівні JVM і не має зворотного зв'язку із середовищем виконання, оскільки є частиною ядра JVM і написаний у машинному коді. Цей завантажувач є батьківським елементом для всіх інших екземплярів ClassLoader.

    В основному відповідає за завантаження внутрішніх класів JDK, зазвичай rt.jar та інших основних бібліотек, розташованих у каталозі $JAVA_HOME/jre/lib . У різних платформ можуть бути різні реалізації цього класу завантажувача.

  2. Extension Classloader - завантажувач розширень, нащадок класу базового завантажувача. Дбає про завантаження розширення стандартних базових класів Java. Завантажується з каталогу розширень JDK, зазвичай $ JAVA_HOME / lib / ext або будь-якого іншого каталогу, згаданого в системній властивості java.ext.dirs (за допомогою цієї опції можна керувати завантаженням розширень).

  3. System ClassLoader - системний завантажувач, реалізований на рівні JRE, який піклується про завантаження всіх класів рівня програми JVM. Він завантажує файли, знайдені у змінному оточенні класів -classpath або -cp опції командного рядка.

Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 - 2Завантажувачі класів - це частина середовища виконання Java. Коли JVM запитує клас, завантажувач класів намагається знайти клас і завантажити визначення класу в середу виконання, використовуючи повне ім'я класу. Метод java.lang.ClassLoader.loadClass() відповідає за завантаження визначення класу під час виконання. Він намагається завантажити клас на основі повного імені. Якщо клас не завантажений, він делегує запит завантажувачу батьківського класу. Цей процес відбувається рекурсивно виглядає так:
  1. System Classloader намагається знайти клас у своєму кеші.

    • 1.1. Якщо клас знайдено, завантаження успішно завершено.

    • 1.2. Якщо клас не знайдено, завантаження делегується до Extension Classloader.

  2. Extension Classloader намагається знайти клас у власному кеші.

    • 2.1. Якщо клас знайдено – успішно завершено.

    • 2.2. Якщо клас не знайдено, завантаження делегується Bootstrap Classloader.

  3. Bootstrap Classloader намагається знайти клас у власному кеші.

    • 3.1. Якщо клас знайдено, завантаження успішно завершено.

    • 3.2. Якщо клас не знайдено, базовий Bootstrap Classloader спробує завантажити його.

  4. Якщо завантаження:

    • 4.1. Пройшла успішно - завантаження класу завершено.

    • 4.2. Не пройшла успішно – керування передається до Extension Classloader.

  5. 5. Extension Classloader намагається завантажити клас, і якщо завантаження:

    • 5.1. Пройшла успішно - завантаження класу завершено.

    • 5.2. Не пройшла успішно – керування передається до System Classloader.

  6. 6. System Classloader намагається завантажити клас, і якщо завантаження:

    • 6.1. Пройшла успішно - завантаження класу завершено.

    • 6.2. Не пройшла успішно – генерується виняток – ClassNotFoundException.

Тема завантажувачів класів велика і не варто нехтувати. Щоб ознайомитися з нею докладніше, раджу прочитати цю статтю , а ми не затримуватимемося і підемо далі.

12. Що таке Run-Time Data Areas?

Run-Time Data Ares - області даних середовища виконання JVM. JVM визначає деякі області даних часу виконання, необхідні під час виконання програми. Одні з них створюються під час запуску JVM. Інші локальні по відношенню до потоків і створюються тільки при створенні потоку (і знищуються, коли потік знищується). Області даних середовища виконання JVM виглядають так: Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 – 3
  • PC Register – регістр ПК – локальний для кожного потоку та містить адресау інструкції JVM, яку потік виконує в даний момент.

  • JVM Stack — це область пам'яті, яка використовується як сховище для локальних змінних і часових результатів. Кожен поток має свій окремий стек: як тільки потік завершується, цей стек також знищується. Варто відзначити, що перевагою stack над heap є продуктивність, у той час як heap безумовно має перевагу в масштабі сховища.

  • Native Method Stack — область даних кожному потоку, у якій зберігаються елементи даних, аналогічні стеку JVM, до виконання власних (не Java) методів.

  • Heap - використовується всіма потоками як сховище, яке містить об'єкти, метадані класів, масиви і т. д., які створюються під час виконання. Ця область створюється під час запуску JVM і знищується після завершення її роботи.

  • Method area — область методу — ця область часу виконання загальна всім потоків і створюється під час запуску JVM. Він зберігає структури кожного класу, такі як пул констант (Runtime Constant Pool — пул для зберігання констант), код для конструкторів і методів, дані методу тощо.

13. Що таке immutable object?

У цій частині статті в 14 і 15 питанні вже є відповідь на це питання, тому ознайомлюєтеся не марнуючи часу.

14. У чому особливість класу String?

Раніше у розборі ми неодноразово говорабо про ті чи інші особливості String (для цього був окремий розділ). Зараз підіб'ємо підсумок по особливостям String :
  1. Це найпопулярніший об'єкт Java, який застосовують для різноманітних цілей. За частотою використання не поступається навіть примітивним типам.

  2. Об'єкт даного класу можна створити без використання ключового слова new безпосередньо через лапки String str = "рядок"; .

  3. String - це immutable клас: при створенні об'єкта даного класу його дані не можна змінити (коли ви до деякого рядка додаєте + "інший рядок", як результат ви отримаєте новий, третій рядок). Незмінність класу String робить його безпечним.

  4. Клас String фіналізований (має модифікатор final ), тому його успадкування неможливе.

  5. String має свій пул рядків, область пам'яті в heap, яка кешує створювані рядкові значення . У цій частині серії , у 62 питанні, я описував рядковий пул.

  6. У Java присутні аналоги String , також призначені для роботи з рядками - StringBuilder і StringBuffer , але з тією відмінністю, що вони змінюються. Докладніше про них ви можете почитати у цій статті .

Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 – 4

15. Що таке підступність типів?

Для розуміння підступності ми розглянемо приклад. Припустимо, у нас є клас тварини:
public class Animal {
 void voice() {
   System.out.println("*тишина*");
 }
}
І деякий розширюючий його клас Dog :
public class Dog extends Animal {

 @Override
 public void voice() {
   System.out.println("Гав, гав, гав!!!");
 }
}
Як ми пам'ятаємо, батьківському типу ми можемо без проблем надавати об'єкти типу спадкоємця:
Animal animal = new Dog();
Це в нас буде ніщо інше, як поліморфізм. Зручно, чи не так? Ну а у випадку зі списком тварин? Чи зможемо ми задати список з дженериком Animal список з об'єктами Dog ?
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
У разі рядок присвоєння списку тварин списку собак буде підкреслено червоним, тобто. компілятор не пропустить цей код. Незважаючи на те, що як це привласнення дуже логічно (адже змінної типу Animal ми можемо привласнити об'єкт Dog ) його зробити не можна. Це відбувається тому, що якби це було припустимо, у список, який спочатку призначений для Dog , ми зможемо покласти об'єкт Animal , при цьому думаючи, що в списку у нас тільки Dogs . І потім, наприклад, візьмемо за допомогою методу get() об'єкт у того списку dogs , думаючи, що це собака, і викличемо у нього деякий метод об'єкта Dog , якого немає уAnimal . І як ви розумієте, це неможливо – впаде помилка. Але, на щастя, компілятор не пропускає цієї логічної помилки з присвоєнням списку нащадків, списку батьків (і навпаки). У Java можливе присвоєння об'єктів списків лише змінним спискам з дженериками, що збігаються. Це називається інваріацією. Якби могли це зробити, це називалося б і називалося підступом. Тобто, коваріація - це якби ми могли змінної типу List<Animal> задати об'єкт типу ArrayList<Dog> . Виходить що Java варіантність не підтримується? Як би не так! Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 – 5Але це робиться своїм особливим шляхом. Для цього використовується конструкція ? extends Animal. Вона ставиться дженериком змінної, якою хочемо задати об'єкт списку, з дженериком нащадка. Ця конструкція дженерика означає, що підійде будь-який тип, який є нащадком типу Animal (і тип Animal також підпадає під це узагальнення). У свою чергу, Animal може бути не тільки класом, але і інтерфейсом (і нехай вас не вводить в оману ключове слово extends ). Наше попереднє присвоєння ми можемо виконати так:
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
В результаті ви побачите в IDE, що компілятор не лаятиметься на цю конструкцію. Давайте перевіримо працездатність цієї конструкції. Припустимо, ми маємо метод, який змушує всіх переданих йому тварин видати звуки:
public static void animalsVoice(List<? extends Animal> animals) {
 for (Animal animal : animals) {
   animal.voice();
 }
}
Передамо йому список із собаками:
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
animalsVoice(dogs);
У консолі ми побачимо наступний висновок:
Гав гав гав!!! Гав гав гав!!! Гав гав гав!!!
А отже, цей підхід до підступності успішно працює. Зазначу, що в список з цим дженеріком ? extends Animal ми не можемо вставити нові дані ніякого типу: ні типу Dog , ні навіть типу Animal :
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
Власне, в останніх двох рядках компілятор підкреслюватиме червоним вставку об'єктів. Це пов'язано з тим, що ми не можемо бути повністю впевнені, перелік з об'єктами якого типу буде присвоєний переліку з даних дженеріком <? extends Animal> . Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 – 6Хотілося б ще розповісти про контраваріантність , тому що зазвичай це поняття завжди йде разом з підступністю, і як правило запитують про них разом. Це — деяка протилежність ковариантности, оскільки даної конструкції використовується тип спадкоємця. Припустимо, нам потрібен список, якому можна буде присвоїти список з типом об'єктів, які не є предками об'єкта Dog. При цьому ми не знаємо, що це будуть за конкретні типи. У такому разі нас може виручити конструкція виду ? super Dog , для якої підходять всі типи - прабатьки класу Dog :
List<Animal> animals = new ArrayList<>();
List<? super Dog> dogs = animals;
dogs.add(new Dog());
dogs.add(new Dog());
Ми можемо сміливо додавати до списку з таким дженериком об'єкти типу Dog , адже у нього в будь-якому випадку є всі реалізовані методи будь-якого його прабатька. Але ми не зможемо додати об'єкт типу Animal , тому що немає впевненості, що всередині будуть саме об'єкти цього типу, а не, наприклад, Dog . Адже ми можемо запитати у елемента даного списку метод класу Dog , якого не буде в наявності у Animal . У разі виникне помилка компіляції. Також, якби ми захотіли реалізувати попередній метод, але вже з цим дженериком:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Dog dog : dogs) {
   dog.voice();
 }
}
ми отримали помилку компіляції в циклі for , оскільки ми можемо бути впевнені, що список містить об'єкти типу Dog і вільно використовувати його методи. Якщо цей перелік ми викличемо метод dogs.get(0); - Ми отримаємо об'єкт типу Object . Тобто для роботи методу animalsVoice() нам як мінімум потрібно додати невеликі маніпуляції із звуженням даних виду:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Object obj : dogs) {
   if (obj instanceof Dog) {
     Dog dog = (Dog) obj;
     dog.voice();
   }
 }
}
Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 – 7

16. Які методи в класі Object?

У цій частині серії, в 11 пункті, я вже відповів на це питання, тому настійно раджу ознайомитися, якщо ви досі цього не зробабо. На цьому сьогодні й закінчимо. До зустрічі у наступній частині! Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 15 – 8
Інші матеріали серії:
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ