Здесь собраны вопросы для закрепления материала по теме исключений и для подготовки к собеседованиям.
Обязательно надо помнить иерархию исключений.
Также могут попросить привести пару примеров исключений каждого типа. Необходимо помнить какие из исключений проверяемые, а какие не проверяемые.
Помнить, что каждое у каждого исключения есть причина, которая хранится в поле cause
, получить ее можно через соответствующий метод:
/**
* Returns the cause of this throwable or {@code null} if the
* cause is nonexistent or unknown. (The cause is the throwable that
* caused this throwable to get thrown.)
*
* <p>This implementation returns the cause that was supplied via one of
* the constructors requiring a {@code Throwable}, or that was set after
* creation with the {@link #initCause(Throwable)} method. While it is
* typically unnecessary to override this method, a subclass can override
* it to return a cause set by some other means. This is appropriate for
* a "legacy chained throwable" that predates the addition of chained
* exceptions to {@code Throwable}. Note that it is <i>not</i>
* necessary to override any of the {@code PrintStackTrace} methods,
* all of which invoke the {@code getCause} method to determine the
* cause of a throwable.
*
* @return the cause of this throwable or {@code null} if the
* cause is nonexistent or unknown.
* @since 1.4
*/
public synchronized Throwable getCause() {
return (cause==this ? null : cause);
}
Помимо причины у исключения может быть еще и сообщение, которое хранится в поле detailMessage
, получить его можно также через соответствующий метод:
/**
* Returns the detail message string of this throwable.
*
* @return the detail message string of this {@code Throwable} instance
* (which may be {@code null}).
*/
public String getMessage() {
return detailMessage;
}
Общие вопросы на понимание.
Вопрос:
Скомпилируется ли данный код:
public class ExceptionExample {
public static void main(String[] args) {
String line; // (1)
try {
line = "hello"; // (2)
} catch (Exception e) {
System.err.println(e);
}
System.out.println(line); // (3)
}
}
Ответ:
Нет.
Java-программист должен проинициализировать переменную, чтобы ею воспользоваться. Компилятор выдаст ошибку, если это не сделать.
В примере выше программист:
- Объявляет переменную
line
- (1) - Присваивает
line
значениеhello
- (2) - Вызывает
System.out.println
с аргументомline
- (3)
Если в блоке try
вылетит исключение, то:
- В блоке
catch
никто значение не присвоит - На третьей строке переменная окажется неинициализированной
Поэтому компилятор считает переменную неинициализированной и выдаёт ошибку на строке (3):
variable line might not have been initialized
System.out.println(line); // (3)
Вопрос:
Видите ли вы проблемы в данном коде:
/**
* Parse time from string to java.util.Date.
* @param time as string
* @return Date object.
*/
public static Date from(String date) {
try {
DateFormat format = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
return format.parse(date);
} catch (ParseException e) {
return new Date();
}
}
Ответ:
Самая серьезная проблема заключается в том, что так реагировать на исключение категорически нельзя.
Потому что происходит подмена результата. Контракт метода говорит о том, что метод преобразовывает дату из строки в объект класса Date
.
Однако, в ситуации, когда в строке содержится невалидные данные, например, "12:22:aa:cc", исключение будет перехвачено и будет возвращен объект с текущей датой (на данные момент). А это абсолютно не то, что ожидается по контракту метода.
Представьте, что я прошу вас преобразовать "Hello World" в дату, а вы, вместо того, чтобы мне явно сказать о невозможности этой операции, говорите мне текущую дату.
Еще одной проблемой можно назвать создание SimpleDateFormat
на каждый вызов метода, хотя формат преобразования даты один и тот же. Такие моменты лучше выносить в поля класса.
Третьей проблемой можно назвать то, что явно никак не обрабатывается и не проверяется ситуация с null
.
Хотя это и не обязательно, но я предпочитаю явно прописывать проверки по null
.
Вопрос:
Может ли возникнуть ситуация, при которой блок finally
не выполнится?
Ответ:
Да, может!
Блок finally
не будет выполнен, если, в код включен предшествующий блоку finally системный выход.
Или произойдет завершение работы JVM
.
try {
System.exit(0);
} catch(Exception e) {
System.err.println("Catch");
} finally {
System.err.println("Finally");
}
Вопрос:
Какой результат выполнения вызова метода:
public int example() {
try {
throw new IllegalArgumentException();
} catch(Exception e) {
System.err.println("Catch");
return 14;
} finally {
return -1;
}
}
Ответ:
Так как блок finally
выполняется всегда, то результатом будет -1
.
Вопрос:
Какой результат выполнения вызова метода:
public int example() {
try {
return 10;
} finally {
throw new RuntimeException();
}
}
Ответ:
Даже несмотря на return
из метода, блок finally
будет выполнен и результатом будет выбрасывание RuntimeException
исключения.
Вопрос:
Можем ли мы бросить java.lang.Throwable
исключение?
А java.lang.OutOfMemoryError
?
Ответ:
Да, как и любое другое исключение.
Вопрос:
Скомпилируется ли данный код:
try {
throw new FileNotFoundException("Ooops");
} catch (FileNotFoundException | IOException e) {
System.err.println("Catch");
}
Ответ:
Нет, данный код не скомпилируется.
Причина в том, что IOException
является родительским классом для FileNotFoundException
, а значит уже на этапе компиляции будет понятно, что можно обойтись одним IOException
, поэтому будет ошибка:
Types in multi-catch must be disjoint: 'java.io.FileNotFoundException' is a subclass of 'java.io.IOException'
Вопрос:
Перепишем пример выше в виде:
try {
throw new FileNotFoundException("Ooops");
} catch (IOException e) {
System.err.println("Catch");
} catch (FileNotFoundException e) {
System.err.println("Catch 2");
}
Что теперь?
Ответ:
Скомпилировано также не будет, так как блок с обработкой FileNotFoundException
недостижим, ведь блок выше, перехватывающий IOException
(родительский для FileNotFoundException
), перехватит все FileNotFoundException
.
Вопрос:
Как исправить ситуацию выше?
Ответ:
Если обработка для FileNotFoundException
отличается от IOException
, то поменять catch
-блоки местами.
Если обработка для обоих исключений одна, то оставить обработку только более общего (в данном случае этоIOException
).
Вопрос:
Коронный вопрос на всех собеседованиях на junior
:
В чем отличия между final
, finally
и finalize
?
Ответ:
Блок finally
необходим для гарантированного действия при обработке исключения.
Метод finalize
был нужен для описания действий при уничтожении объекта сборщиком мусора, но сейчас практически не используется и вот почему.
Ключевое слово final
применяется для того, чтобы явно сказать, что метод/переменная/класс являются законченными и не должны быть переопределены/перезаписаны/участвовать в наследовании.
Об этом тут.