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 . Є три види спадкоємців:-
Bootstrap ClassLoader - базовий завантажувач, реалізований на рівні JVM і не має зворотного зв'язку із середовищем виконання, оскільки є частиною ядра JVM і написаний у машинному коді. Цей завантажувач є батьківським елементом для всіх інших екземплярів ClassLoader.
В основному відповідає за завантаження внутрішніх класів JDK, зазвичай rt.jar та інших основних бібліотек, розташованих у каталозі $JAVA_HOME/jre/lib . У різних платформ можуть бути різні реалізації цього класу завантажувача. -
Extension Classloader - завантажувач розширень, нащадок класу базового завантажувача. Дбає про завантаження розширення стандартних базових класів Java. Завантажується з каталогу розширень JDK, зазвичай $ JAVA_HOME / lib / ext або будь-якого іншого каталогу, згаданого в системній властивості java.ext.dirs (за допомогою цієї опції можна керувати завантаженням розширень).
-
System ClassLoader - системний завантажувач, реалізований на рівні JRE, який піклується про завантаження всіх класів рівня програми JVM. Він завантажує файли, знайдені у змінному оточенні класів -classpath або -cp опції командного рядка.
-
System Classloader намагається знайти клас у своєму кеші.
-
1.1. Якщо клас знайдено, завантаження успішно завершено.
-
1.2. Якщо клас не знайдено, завантаження делегується до Extension Classloader.
-
-
Extension Classloader намагається знайти клас у власному кеші.
-
2.1. Якщо клас знайдено – успішно завершено.
-
2.2. Якщо клас не знайдено, завантаження делегується Bootstrap Classloader.
-
-
Bootstrap Classloader намагається знайти клас у власному кеші.
-
3.1. Якщо клас знайдено, завантаження успішно завершено.
-
3.2. Якщо клас не знайдено, базовий Bootstrap Classloader спробує завантажити його.
-
-
Якщо завантаження:
-
4.1. Пройшла успішно - завантаження класу завершено.
-
4.2. Не пройшла успішно – керування передається до Extension Classloader.
-
-
5. Extension Classloader намагається завантажити клас, і якщо завантаження:
-
5.1. Пройшла успішно - завантаження класу завершено.
-
5.2. Не пройшла успішно – керування передається до System Classloader.
-
-
6. System Classloader намагається завантажити клас, і якщо завантаження:
-
6.1. Пройшла успішно - завантаження класу завершено.
-
6.2. Не пройшла успішно – генерується виняток – ClassNotFoundException.
-
12. Що таке Run-Time Data Areas?
Run-Time Data Ares - області даних середовища виконання JVM. JVM визначає деякі області даних часу виконання, необхідні під час виконання програми. Одні з них створюються під час запуску JVM. Інші локальні по відношенню до потоків і створюються тільки при створенні потоку (і знищуються, коли потік знищується). Області даних середовища виконання JVM виглядають так:-
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 :-
Це найпопулярніший об'єкт Java, який застосовують для різноманітних цілей. За частотою використання не поступається навіть примітивним типам.
-
Об'єкт даного класу можна створити без використання ключового слова new безпосередньо через лапки String str = "рядок"; .
-
String - це immutable клас: при створенні об'єкта даного класу його дані не можна змінити (коли ви до деякого рядка додаєте + "інший рядок", як результат ви отримаєте новий, третій рядок). Незмінність класу String робить його безпечним.
-
Клас String фіналізований (має модифікатор final ), тому його успадкування неможливе.
-
String має свій пул рядків, область пам'яті в heap, яка кешує створювані рядкові значення . У цій частині серії , у 62 питанні, я описував рядковий пул.
-
У Java присутні аналоги String , також призначені для роботи з рядками - StringBuilder і StringBuffer , але з тією відмінністю, що вони змінюються. Докладніше про них ви можете почитати у цій статті .
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 варіантність не підтримується? Як би не так! Але це робиться своїм особливим шляхом. Для цього використовується конструкція ? 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);
У консолі ми побачимо наступний висновок:
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
Власне, в останніх двох рядках компілятор підкреслюватиме червоним вставку об'єктів. Це пов'язано з тим, що ми не можемо бути повністю впевнені, перелік з об'єктами якого типу буде присвоєний переліку з даних дженеріком <? extends Animal> . Хотілося б ще розповісти про контраваріантність , тому що зазвичай це поняття завжди йде разом з підступністю, і як правило запитують про них разом. Це — деяка протилежність ковариантности, оскільки даної конструкції використовується тип спадкоємця. Припустимо, нам потрібен список, якому можна буде присвоїти список з типом об'єктів, які не є предками об'єкта 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();
}
}
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ