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() — метод, который освобождает мьютекс и переводит текущий поток в режим ожидания как бы прикрепляясь к текущему монитору (что-то вроде якоря). Из-за этого данный метод может быть вызван только из synhronized блока или метода (иначе что ему освобождать и чего ему ожидать).
Также отмечу, что это метод класса Object.
Точнее не один, а даже три:Wait() — переводит текущий поток в режим ожидания пока другой поток не вызовет метод notify() или notifyAll() для этого объекта (об этих методах поговорим дальше).
Wait (long timeout) — переводит текущий поток в режим ожидания пока другой поток не вызовет метод notify() или notifyAll() для этого объекта или не истечет указанное время — timeout.
Wait (long timeout, int nanos) — аналогичен предыдущему, только nanos позволяет указать наносекунды (более точная настройка времени).
Notify() — метод позволяющий пробудить один рандомный поток текущего блока синхронизации. Опять же, может быть вызван только в synhronized блоке или методе (ведь в других местах ему не будет кого размораживать).
NotifyAll() — метод, пробуждающий все ожидающие потоки на текущем мониторе (тоже используется только в synhronized блоке или методе).
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("Конец выполнения программы");
Итог выполнения будет таким же, как и в первом случае, но данный подход мне нравится больше: мы пишем меньше кода и больше используем уже готовый, стандатный функционал.
На этом сегодня и остановимся.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ