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

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

Стаття з групи Random UA
Вітання! Знання – сила. Чим більше знань буде у вас до першої вашої співбесіди, тим впевненіше на ній почуватиметеся. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 - 1З хорошим багажем знань вас буде складно спантеличити, і в той же час ви зможете приємно здивувати вашого інтерв'юера. Тому сьогодні ми без зайвих передмов продовжимо посилювати вашу теоретичну базу, розбираючи 250+ питань для Java-розробника . Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 - 2

103. Які правила для перевірки винятків при наслідуванні?

Якщо я правильно розумію питання, тут запитують про правила роботи з винятками під час наслідування, і вони такі:
  • Перевизначуваний або реалізований метод у спадкоємці/реалізації не може викидати контрольовані винятки, які вищі за ієрархією ніж винятки у методі суперкласу/інтерфейсу.
Тобто якщо у нас є інтерфейс Animal з методом, що кидає IOException :
public  interface Animal {
   void voice() throws IOException;
}
У реалізації даного інтерфейсу ми не можемо виставити більш загальний викид (наприклад, Exception , Throwable ), але можемо замінити його на виключення спадкоємця, як, наприклад, на FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • Конструктор підкласу повинен включити у свій блок throws всі класи винятків конструктором суперкласу, що прокидається, до якого йде звернення при створенні об'єкта.
Припустимо, що конструктор класу Animal кидає безліч винятків:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Тоді спадкоємець класу в конструкторі також повинен їх вказати:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Або, як і у випадку з методами, можна вказати не ті ж винятки, а загальніші. У нашому випадку достатньо буде вказати загальніший виняток — Exception , оскільки це загальний предок всіх розглянутих трьох винятків:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Чи могли б ви написати код, коли блок finally не буде виконаний?

Для початку згадаємо, що таке finally . Раніше ми розглядали механізм перехоплення винятків: блок try окреслює зону перехоплення, водночас блок (блоки) catch — код, який спрацює при падінні певного виключення. Finally — третій блок коду після слова finally , який взаємозамінний з catch , але не взаємовиключним. Суть даного блоку полягає в тому, що код у ньому спрацьовує завжди, незалежно від результату спрацьовування try або catch .(незалежно від того чи впав виняток чи ні). Випадки його неспрацьовування дуже рідкісні і є нештатними. Найпростіший випадок неспрацьовування - це коли в коді вище буде викликаний метод System.exit(0) , який завершує програму (гасить її):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
Також є деякі інші ситуації, у яких finally не спрацює:
  • Аварійне завершення програми, викликане критичними неполадками системи, або падіння деякої Error, яка "упустить" програму (прикладом error може стати та ж StackOwerflowError , що виникає при переповненні пам'яті стека).
  • Коли через блок ry…finally проходить deamon -потік та паралельно з цим програма завершується. Адже deamon - поток - це потік для фонових дій, тобто він не є пріоритетним і обов'язковим і додаток не чекатиме закінчення його роботи.
  • Найбанальніший нескінченний цикл, в try або catch , потрапивши в який потік там залишиться назавжди:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

Це питання дуже популярне на співбесідах новачків, тому кілька цих виняткових ситуацій варто пам'ятати. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 – 3

105. Напишіть приклад обробки кількох винятків в одному блоці catch

1) Можливо питання було поставлене некоректно. Наскільки я розумію в даному питанні мається на увазі безліч catch для одного блоку try :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
Якщо в блоці try падає виняток, то блоки catch почергово зверху вниз намагаються його перехопити, якщо в деякого блоку catch це виходить, він отримує право на обробку виключення, в той час коли інші блоки нижче не зможуть спробувати перехопити і по своєму обробити його. Тому вужчі винятки ставляться в ланцюжку catch блоків вище, а ширші — нижчі. Наприклад, якщо у нас у першому catch блоці відловлюється виняток класу Exception , то в інші блоки виключення, що перевіряються, ніяк не зможуть (інші блоки з спадкоємцями Exception будуть абсолютно марні). 2) Питання було поставлено коректно У такому разі наша обробка матиме вигляд на кшталт наступного:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
Зловивши виняток через catch , ми намагаємося з'ясувати його конкретний тип через метод instanceof , який використовується для перевірки – чи належить об'єкт певному типу, щоб потім без негативних наслідків можна було звузити його до цього типу. Обидва розглянуті підходи можуть застосовуватися в одній і тій же ситуації, але про некоректність питання я сказав через те, що другий варіант я б не назвав добрим і у своїй практиці жодного разу не зустрічав, водночас перший спосіб з мультикетчами отримав широке поширення. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 – 4

106. Який оператор дозволяє примусово викинути виняток? Напишіть приклад

Вище я вже неодноразово використовував, проте повторюю це ключове слово - throw . Приклад використання (примусове викидання виключення):
throw new NullPointerException();

107. Чи може метод main викинути throws-выключение? Якщо так, то куди передасть?

В першу чергу хочу відзначити, що main це не більше ніж звичайний метод, і так, він викликається віртуальною машиною для початку виконання програми, але крім цього він може бути викликаний і з будь-якого іншого коду. Тобто на нього також поширюються звичайні правила вказівки винятків, що викидаються (checked exceptions) після throws :
public static void main(String[] args) throws IOException {
Відповідно в ньому також можуть падати винятки. Якщо main не був викликаний в якомусь методі, а був запущений як точка запуску програми, викинутий ним виняток буде оброблятися перехоплювачем .UncaughtExceptionHandler . Цей обробник один на свій потік (тобто по одному обробнику в кожному потоці). При потребі можна створити свій обробник і задати його за допомогою методу setDefaultUncaughtExceptionHandler , викликаного у об'єкта Thread .

Багатопоточність

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

108. Які засоби для роботи з багатопоточністю знаєте?

Основні/базові засоби для використання багатопоточності в Java:
  • Synchronized — механізм закриття (блокування) методу/блоку при вході до нього потоку, від інших потоків.
  • Volatile – механізм забезпечення узгодженого доступу до змінної різними потоками, тобто з наявністю даного модифікатора у змінної всі операції присвоєння та читання її мають бути атомарними. Тобто потоки не копіюватимуть цю змінну в свою локальну пам'ять і змінювати її, а змінюватимуть її початкове значення.
Докладніше про volatile читайте ось тут .
  • Runnable - інтерфейс, реалізувавши який (зокрема його метод run) у деякому класі:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
І створивши об'єкт цього класу, можна запустити новий потік, задавши цей об'єкт у конструктор нового об'єкта Thread , і викликавши метод start() :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
Метод start запускає реалізований метод run() окремому потоці.
  • Thread - клас, успадкувавши від якого (при цьому перевизначивши метод run ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
І створивши об'єкт даного класу і запустивши його за допомогою методу start() , запустимо тим самим новий потік:
new CustomThread().start();
  • Concurrency — пакет із інструментами для роботи у багатопотоковому середовищі.
Він складається з:
  • Concurrent Collections – набір колекцій, спеціалізованих під роботу у багатопотоковому середовищі.
  • Queues - спеціалізовані черги для багатопотокового середовища (блокуючі та неблокуючі).
  • Synchronisers – спеціалізовані утиліти для роботи у багатопотоковому середовищі.
  • Executors – механізми для створення пулів потоків.
  • Locks - механізми синхронізації потоків (гнучкіші ніж стандартні - synchronized, wait, notify, notifyAll).
  • Atomics – класи оптимізовані під багатопоточне виконання, саме у них кожна операція – атомарна.
Докладніше про concurrent пакет читайте ось тут .

109. Розкажіть про синхронізацію між потоками. Для чого використовують методи wait(), notify() - notifyAll() join()?

Наскільки я зрозумів питання, синхронізація між потоками — це про ключового модифікатора synhronized . Цей модифікатор можна поставити або безпосередньо біля блоку:
synchronized (Main.class) {
   // некоторая логика
}
Або у сигнатурі способу:
public synchronized void move() {
   // некоторая логика}
Як я і сказав раніше, synhronized це механізм, що дозволяє закривати блок / метод від інших потоків, коли в нього вже зайшов один потік. Представимо блок/метод як кімнату. Деякий потік, прийшовши до неї, зайде в неї і закриє її на ключ, інші потоки, прийшовши до кімнати і побачивши, що вона зачинена, чекатимуть біля неї, поки вона не звільниться. Зробивши свої справи, перший потік виходить із кімнати та звільняє ключ. І я не дарма постійно говорив про ключ, адже він справді існує. Це спеціальний об'єкт, який має стан зайнятий/вільний. Цей об'єкт прикріплений до кожного об'єкта Java, тому при використанні synhronized блоку нам потрібно в дужках вказувати об'єкт, на чий м'ютекс ми хочемо зачиняти двері.
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
Також можна використовувати м'ютекс класу, як я це зробив у першому прикладі ( Main.class ). Коли ми використовуємо synhronized на методі, адже ми не вказуємо об'єкт на якому хочемо закрити правильно? У такому випадку для нестатичного методу закриватиметься на м'ютекс об'єкта this , тобто поточний об'єкт даного класу. Статичний закриватиметься на м'ютекс поточного класу ( this.getClass(); ). Детальніше про м'ютекс можна почитати ось тут . Ну а про synhronized читати ось тут . Wait()- метод, який звільняє м'ютекс і переводить поточний потік в режим очікування як би прикріплюючись до поточного монітора (щось на зразок якоря). Через це цей метод може бути викликаний тільки з синхронізованого блоку або методу (інакше, що йому звільняти і чого йому чекати). Також наголошу, що це метод класу Object . Точніше не один, а навіть три:
  • Wait() — переводить поточний потік у режим очікування доки інший потік не викличе метод notify() або notifyAll() для цього об'єкта (про ці методи поговоримо далі).

  • Wait (long timeout) — переводить поточний потік у режим очікування доки інший потік не викличе метод notify() або notifyAll() для цього об'єкта або не закінчиться вказаний час — timeout .

  • Wait (long timeout, int nanos) - аналогічний попередньому, тільки nanos дозволяє вказати наносекунди (точніше налаштування часу).

  • Notify() — метод, який дозволяє пробудити один рандомний потік поточного блоку синхронізації. Знову ж таки, може бути викликаний тільки в синхронізованому блоці або методі (адже в інших місцях йому не буде кого розморожувати).

  • NotifyAll() — метод, що пробуджує всі очікувані потоки на поточному моніторі (теж використовується лише у синхронізованому блоці або методі).

110. Як зупинити потік?

Перше, що треба сказати, - це те, що при повному виконанні методу run() потік автоматично знищується. Але іноді треба вбити його достроково, до завершення цього методу. І що ж тоді робити? Можливо, у об'єкта Thread використовувати метод stop() ? Як би не так! Цей метод вважається застарілим і може призвести до збоїв системи. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 - 6Ну, а що тоді? Є два способи зробити це: Перший - використання свого внутрішнього логічного прапора. Давайте розглянемо на прикладі. У нас є своя реалізація потоку, який має виводити на екран якусь фразу до повної зупинки:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
З використанням методу stopRunning() внутрішній прапор стає false і метод run припиняє свою роботу. Давайте запустимо його в main :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
У результаті в консолі ми побачимо щось на кшталт цього:
Початок виконання програми Потік виконує якусь логіку... Потік виконує якусь логіку... Потік виконує якусь логіку... Потік виконує якусь логіку... Потік виконує якусь логіку... Потік виконує якусь логіку... Кінець виконання програми Потік зупинено !
А це означає, що наш потік відпрацював, вивів кілька повідомлень у консоль і був успішно зупинений. Зазначу, що кількість виведених повідомлень від запуску до запуску буде різною, іноді додатковий потік навіть нічого не виводив. Як я помітив, це залежить від часу сну основного потоку, чим він більший, тим менший шанс, що додатковий потік нічого не виведе. При часі сну 1мс виведення повідомлень майже ніколи не відбувається, але якщо виставити 20мс майже завжди спрацьовує. Можливо, за малого часу потік просто не встигає запуститися і розпочати свою роботу, а його одразу ж зупиняють. Другий спосіб полягає у використанні на об'єкті Thread методу interrupted(), який повертає значення внутрішнього прапора переривання (за замовчуванням цей прапор false ) та іншого його методу interrupt() - ставить цей прапор у true (коли цей прапор true потік повинен зупинити свою роботу). Дивимося приклад:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
Запуск у main :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
Підсумок виконання буде таким самим, як і в першому випадку, але цей підхід мені подобається більше: ми пишемо менше коду і більше використовуємо вже готовий, стандатний функціонал. Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 – 7На цьому сьогодні й зупинимося.Розбір запитань та відповідей із співбесід на Java-розробника.  Частина 12 – 8
Інші матеріали серії:
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ