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

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

Стаття з групи Random UA
Салют! Світ постійно рухається і постійно рухаємось ми. Раніше, щоб стати Java-розробником, було досить небагато знати синтаксис Java, ну а інше прийде. Згодом рівень знань, необхідний становлення Java-розробника, значно зріс, як і конкуренція, яка продовжує рухати нижню планку необхідних знань нагору. Якщо ви дійсно хочете стати розробником, це потрібно прийняти як даність і ретельно підготуватися ,Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 - 1 щоб виділятися серед таких же, як і ви, новачків.. У попередніх статтях ми розібрали всі питання рівня джуна, і сьогодні візьмемося за питання рівня мідл. Хоча я зазначу, що вони не є стовідсотковими питаннями рівня мідла, більшість із них ви можете зустріти на співбесіді джуніор рівня, бо саме на таких співбесідах йде докладне промацування вашої теоретичної бази, тоді як у мідла вже питання орієнтовані на промацування його досвіду. . Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 - 2Але, без зайвої передмови, починайте.

Middle

Загальні

1. У чому переваги та недоліки ООП, якщо порівнювати з процедурним/функціональним програмуванням?

У розборі питань для Juinior було це питання, і відповідно я вже відповів на нього. Шукайте це питання та відповідь на нього у цій частині статті, питання 16 та 17.

2. Чим відрізняється агрегація композиції?

У ОВП є кілька видів взаємодії об'єктів, об'єднаних під загальним поняттям "Has-A Relationship". Це ставлення свідчить про те, що один об'єкт є складовою іншого об'єкта. При цьому існує два підвиди цього відношення: Композиція - один об'єкт створює інший об'єкт і час життя іншого об'єкта залежить від часу, що створив. Агрегація - об'єкт отримує посилання (покажчик) на інший об'єкт у процесі конструювання (при цьому час життя іншого об'єкта не залежить від часу життя, що створив). Для більшого розуміння, розглянемо конкретний приклад. У нас є деякий клас машини — Car , у якого в свою чергу є внутрішні поля типу — двигун — Engine та список пасажирів —List<Passenger> , також у нього є метод початку руху - startMoving() :
public class Car {

 private Engine engine;
 private List<Passenger> passengers;

 public Car(final List<Passenger> passengers) {
   this.engine = new Engine();
   this.passengers = passengers;
 }

 public void addPassenger(Passenger passenger) {
   passengers.add(passenger);
 }

 public void removePassengerByIndex(Long index) {
   passengers.remove(index);
 }

 public void startMoving() {
   engine.start();
   System.out.println("Машина начала своё движение");
   for (Passenger passenger : passengers) {
     System.out.println("В машине есть пассажир - " + passenger.getName());
   }
 }
}
У даному випадку Композицією є зв'язок між Car і Engine , оскільки працездатність машини безпосередньо залежить від наявності об'єкта двигуна, адже якщо engine = null , то ми отримаємо NullPointerException . У свою чергу, двигун не може існувати без машини (навіщо нам двигун без машини?) і не може належати кільком машинам в один момент часу. Це означає те, що якщо ми видалимо об'єкт Car , то на об'єкт Engine не буде більше посилань, і незабаром його видалить Garbage Collector . Як ви бачите, цей зв'язок є дуже суворим (сильним). Агрегацією є зв'язок між Carі Passenger , оскільки працездатність Car аж ніяк не залежить від об'єктів типу Passenger та їх кількості. Вони можуть виходити з машини — removePassengerByIndex(Long index) , так і заходити нові — addPassenger(Passenger passenger) , незважаючи на це, функціонування машини продовжиться належним чином. У свою чергу, об'єкти Passenger можуть існувати і без Car Car . Як ви розумієте, це набагато слабкіший зв'язок, ніж ми бачимо у композиції. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 – 3Але це ще не все, об'єкт, який пов'язаний з агрегацією з іншим, може також мати дану зв'язок з іншими об'єктами в той самий момент часу. Наприклад, ви як Java-студент, записані на курси англійської, ОВП та логарифми в один і той же момент часу, але при цьому ви не є критично необхідною частиною їх, без якої неможливе нормальне функціонування (наприклад, викладач).

3. Які патерни GoF ви використовували практично? Наведіть приклади.

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

4. Що таке проксі-об'єкт? Наведіть приклади

Проксі - це структурний патерн проектування, що дозволяє підставляти замість реальних об'єктів спеціальні об'єкти-замінники, або іншими словами проксі-об'єкти. Ці проксі-об'єкти перехоплюють виклики до початкового об'єкта, дозволяючи вклинити деяку логіку до або після передачі оригіналу. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 – 4Приклади використання об'єкта-проксі:
  • Як віддалений проксі – використовується, коли нам потрібний віддалений об'єкт (об'єкт в іншому адресаному просторі), який необхідно уявити локально. У цьому випадку проксі займатиметься створенням з'єднання, кодування, декодуванням та іншим, у той час як клієнт використовуватиме його як ніби початковий об'єкт, що знаходиться в локальному просторі.

  • Як віртуальний проксі – використовується, коли потрібний ресурсомісткий об'єкт. У такому разі об'єкт-проксі служить чимось подібним до зображення реального об'єкта, якого насправді ще немає. Коли ж даному об'єкту відправляється реальний запит (виклик методу), лише тоді відбувається завантаження оригінального об'єкта та виконання методу. Даний підхід ще називається відкладеною ініціалізацією, це буває дуже зручно, адже в деяких ситуаціях оригінальний об'єкт може і не стати в нагоді, тоді і витрат на його створення не буде.

  • Як захисний проксі – використовується, коли потрібно контролювати доступ до деякого об'єкта на основі прав клієнта. Тобто якщо клієнт з недостатнім правом доступу спробує звернутися до оригінального об'єкта, проксі його перехопить і не пустить.

Розглянемо приклад віртуального проксі: Ми маємо деякий інтерфейс оброблювача:
public interface Processor {
 void process();
}
Реалізація якого задіює занадто багато ресурсів, але при цьому він може бути використаний не при кожному запуску програми:
public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
Клас проксі:
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;

 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
Запустимо його в main :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального об'єкта, ещё не сущетсвует
// но при этом есть об'єкт, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, об'єкт оригинал был создан
Зазначу, що проксування використовують багато фреймворків, а для Spring це і зовсім ключовий патерн (Spring прошитий їм вздовж і впоперек). Докладніше про цей патерн читайте ось тут . Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 – 5

5. Які нововведення анонсовано у Java 8?

Нововведення принесені Java 8:
  • Додані функціональні інтерфейси, про те, що це за звір читайте тут .

  • Лямбда-вирази, які тісно пов'язані з функціональними інтерфейсами, докладніше про їхнє використання ось тут .

  • Додано Stream API для зручної обробки колекцій даних, детальніше читайте тут .

  • Додані посилання на методи .

  • У Iterable інтерфейс додано метод forEach() .

  • Доданий новий API дати та часу в пакеті java.time , детальний розбір тут .

  • Поліпшабо Concurrent API .

  • Додавання класу обгортки Optional , який використовується для коректної обробки нульових значень, чудову статтю на цю тему ви знайдете ось тут .

  • Додавання інтерфейсів можливості використовувати static і default методи (що, по суті, наближає Java до множинного успадкування), докладніше тут .

  • Додали нові методи до класу Collection(removeIf(), spliterator()) .

  • Дрібні покращення Java Core.

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

6. Що таке High Cohesion та Low Coupling? Наведіть приклади.

High Cohesion або Висока пов'язаність - це поняття, коли певний клас містить елементи, які тісно пов'язані один з одним та об'єднані за своїм призначенням. Наприклад, усі методи в класі User повинні представляти поведінку користувача. Клас має низьку зв'язність, якщо він містить незв'язані елементи. Наприклад, клас User , який містить метод валідації адресаи електронної пошти:
public class User {
private String name;
private String email;

 public String getName() {
   return this.name;
 }

 public void setName(final String name) {
   this.name = name;
 }

 public String getEmail() {
   return this.email;
 }

 public void setEmail(final String email) {
   this.email = email;
 }

 public boolean isValidEmail() {
   // некоторая логика валидации емейла
 }
}
Клас користувача може нести відповідальність за збереження адресаи електронної пошти користувача, але не за його перевірку або надсилання електронного листа. Тому, щоб досягти високої зв'язності, виносимо метод валідації в окремий клас утиліту:
public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
І використовуємо при необхідності (наприклад, перед збереженням user-у). Low Coupling або Низьке зачеплення - це поняття, що описує низьку взаємозалежність між програмними модулями. По суті, взаємозалежність у тому, як зміна одного вимагає зміни іншого. Два класи мають сильний зв'язок (або щільний зв'язок), якщо вони тісно пов'язані. Наприклад, два конкретні класи, що зберігають посилання один на одного і викликають методи один одного. Слабозв'язані класи простіші у розробці та підтримці. Оскільки вони незалежні один від одного, їх можна розробляти та тестувати паралельно. Крім того, вони можуть бути змінені та оновлені, не впливаючи один на одного. Розглянемо приклад сильно пов'язаних класів. У нас є певний клас студента:
public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
Який містить у собі список уроків:
public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
Кожен урок містить посилання на студентів. Неймовірно сильне зчеплення вам так не здається? Як же можна зменшити його? По-перше, зробимо так, щоб студенти мали не список предметів, а список їх ідентифікаторів:
public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
По-друге, класу уроку зовсім не обов'язково знати про всіх студентів, тому зовсім видалимо їх список:
public class Lesson {
 private Long id;
 private String name;
}
Так стало набагато простіше, і зв'язок став набагато слабшим, ви не знаходите? Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 – 7

ООП

7. Як можна реалізувати множинне успадкування в Java?

Множинне успадкування - це особливість об'єктно-орієнтованої концепції, коли клас може успадковувати властивості більш ніж одного батьківського класу. Проблема виникає, коли існують методи з однаковою сигнатурою як у суперкласах, і у підкласі. При виклику методу компілятор неспроможна визначити, який метод класу має бути викликаний, і навіть за виклику методу класу, який отримує пріоритет. Тому множинне успадкування Java не підтримує! Але є свого роду лазівка, про яку ми й поговоримо далі. Як я і згадав раніше, з виходом Java 8, інтерфейсам було додано можливість мати методи за замовчуванням - defaultметоди. Якщо імплементуючий інтерфейс клас не перевизначає даний метод, то буде використана дана реалізація за умовчанням (перевизначати дефолтний метод не обов'язково, наприклад імплементувати абстрактний). У такому разі, можливе імплементація різних інтерфейсів одним класом та використання їх методів за умовчанням. Розглянемо приклад. У нас є деякий інтерфейс літуна, з default методом fly() :
public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
Інтерфейс ходуна, з default методом walk() :
public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
Інтерфейс плавуна, з методом swim() :
public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
А тепер імплементуємо все це в одному класі качки:
public class Duck implements Flyer, Swimmer, Walker {
}
І запустимо, всі методи нашої качки:
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
У консолі ми отримаємо:
Я ходжу!!! Я лечу!!! Я пливу!
А це означає, що ми правильно зобразабо множинне спадкування, хоч це й не воно. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 – 8Також зазначу, якщо клас імплементуватиме інтерфейси з дефолтними методами, які мають однакові назви методів і однакові аргументи в цих методах, то компілятор почне лаятися на несумісність, тому що він не розуміє який метод дійсно потрібно застосовувати. Виходів тут кілька:
  • Перейменуйте методи в інтерфейсах, щоб вони відрізнялися між собою.
  • Перевизначити такі спірні методи у класі імплементації.
  • Наслідувати клас, який реалізує дані спірні методи (тоді ваш клас буде використовувати саме його реалізацію).

8. Яка різниця між методами final, finally та finalize()?

final — це ключове слово, яке використовується для накладання обмежень на клас, метод або змінну, що означає:
  • Для змінної - після первинної ініціалізації змінну перевизначити не можна.
  • Для методу метод не може бути перевизначений у підкласі (класі спадкоємці).
  • Для класу клас не може бути успадкований.
finally - це ключове слово перед блоком з кодом, що використовується при обробці винятків, спільно з блоком try і спільно (або взаємозамінно) з блоком catch. Код у цьому блоці виконується у будь-якому випадку, незалежно від того, чи буде викинуто виняток чи ні. У цій частині статті, у 104-му питанні розібрано виняткові ситуації у яких даний блок виконано не буде. finalize() — метод класу Object , що викликається перед видаленням кожного об'єкта збирачем сміття, буде викликаний цей метод (наостанок), використовується для очищення ресурсів, що займаються. Докладніше про методи класу Object , які успадковує кожен об'єкт дивіться в цій частиністатті у 11-му питанні. Ну, а на цьому ми сьогодні й закінчимо. До зустрічі у наступній частині! Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 14 – 9
Інші матеріали серії:
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ