103. Які правила для перевірки винятків при наслідуванні?
Якщо я правильно розумію питання, тут запитують про правила роботи з винятками під час наслідування, і вони такі:- Перевизначуваний або реалізований метод у спадкоємці/реалізації не може викидати контрольовані винятки, які вищі за ієрархією ніж винятки у методі суперкласу/інтерфейсу.
public interface Animal {
void voice() throws IOException;
}
У реалізації даного інтерфейсу ми не можемо виставити більш загальний викид (наприклад, Exception , Throwable ), але можемо замінити його на виключення спадкоємця, як, наприклад, на FileNotFoundException :
public class Cat implements Animal {
@Override
public void voice() throws FileNotFoundException {
// некоторая реализация
}
}
- Конструктор підкласу повинен включити у свій блок throws всі класи винятків конструктором суперкласу, що прокидається, до якого йде звернення при створенні об'єкта.
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("Данное сообщение не будет выведенно в консоль"); }
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 , який використовується для перевірки – чи належить об'єкт певному типу, щоб потім без негативних наслідків можна було звузити його до цього типу. Обидва розглянуті підходи можуть застосовуватися в одній і тій же ситуації, але про некоректність питання я сказав через те, що другий варіант я б не назвав добрим і у своїй практиці жодного разу не зустрічав, водночас перший спосіб з мультикетчами отримав широке поширення.
106. Який оператор дозволяє примусово викинути виняток? Напишіть приклад
Вище я вже неодноразово використовував, проте повторюю це ключове слово - throw . Приклад використання (примусове викидання виключення):throw new NullPointerException();
107. Чи може метод main викинути throws-выключение? Якщо так, то куди передасть?
В першу чергу хочу відзначити, що main це не більше ніж звичайний метод, і так, він викликається віртуальною машиною для початку виконання програми, але крім цього він може бути викликаний і з будь-якого іншого коду. Тобто на нього також поширюються звичайні правила вказівки винятків, що викидаються (checked exceptions) після throws :public static void main(String[] args) throws IOException {
Відповідно в ньому також можуть падати винятки. Якщо main не був викликаний в якомусь методі, а був запущений як точка запуску програми, викинутий ним виняток буде оброблятися перехоплювачем .UncaughtExceptionHandler . Цей обробник один на свій потік (тобто по одному обробнику в кожному потоці). При потребі можна створити свій обробник і задати його за допомогою методу setDefaultUncaughtExceptionHandler , викликаного у об'єкта Thread .
Багатопоточність
108. Які засоби для роботи з багатопоточністю знаєте?
Основні/базові засоби для використання багатопоточності в Java:- Synchronized — механізм закриття (блокування) методу/блоку при вході до нього потоку, від інших потоків.
- 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 – класи оптимізовані під багатопоточне виконання, саме у них кожна операція – атомарна.
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() ? Як би не так! Цей метод вважається застарілим і може призвести до збоїв системи. Ну, а що тоді? Є два способи зробити це: Перший - використання свого внутрішнього логічного прапора. Давайте розглянемо на прикладі. У нас є своя реалізація потоку, який має виводити на екран якусь фразу до повної зупинки: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("Конец выполнения программы");
У результаті в консолі ми побачимо щось на кшталт цього:
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("Конец выполнения программы");
Підсумок виконання буде таким самим, як і в першому випадку, але цей підхід мені подобається більше: ми пишемо менше коду і більше використовуємо вже готовий, стандатний функціонал. На цьому сьогодні й зупинимося.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ