«1. Обзор

Исключения — важная тема, с которой должен быть знаком каждый Java-разработчик. В этой статье даны ответы на некоторые вопросы, которые могут возникнуть во время собеседования.

2. Вопросы

Q1. Что такое исключение?

Исключение — это ненормальное событие, возникающее во время выполнения программы и нарушающее нормальный ход выполнения инструкций программы.

Q2. Какова цель ключевых слов Throw и Throws?

Ключевое слово throws используется для указания того, что метод может вызвать исключение во время его выполнения. Он обеспечивает явную обработку исключений при вызове метода:

public void simpleMethod() throws Exception {
    // ...
}

Ключевое слово throw позволяет нам генерировать объект исключения, чтобы прервать нормальный ход программы. Чаще всего это используется, когда программа не удовлетворяет заданному условию:

if (task.isTooComplicated()) {
    throw new TooComplicatedException("The task is too complicated");
}

Q3. Как вы можете обработать исключение?

С помощью оператора try-catch-finally:

try {
    // ...
} catch (ExceptionType1 ex) {
    // ...
} catch (ExceptionType2 ex) {
    // ...
} finally {
    // ...
}

Блок кода, в котором может возникнуть исключение, заключен в блок try. Этот блок также называют «защищенным» или «защищенным» кодом.

Если возникает исключение, выполняется блок catch, соответствующий выброшенному исключению, в противном случае все блоки catch игнорируются.

Блок finally всегда выполняется после выхода из блока try, вне зависимости от того, было ли внутри него сгенерировано исключение.

Q4. Как вы можете поймать несколько исключений?

Существует три способа обработки нескольких исключений в блоке кода.

Во-первых, используйте блок catch, который может обрабатывать все типы выбрасываемых исключений:

try {
    // ...
} catch (Exception ex) {
    // ...
}

Вы должны иметь в виду, что рекомендуемая практика заключается в использовании максимально точных обработчиков исключений.

Слишком широкие обработчики исключений могут сделать ваш код более подверженным ошибкам, перехватывать непредвиденные исключения и вызывать непредвиденное поведение вашей программы.

Второй способ заключается в реализации нескольких блоков catch:

try {
    // ...
} catch (FileNotFoundException ex) {
    // ...
} catch (EOFException ex) {
    // ...
}

Обратите внимание, что если исключения имеют отношение наследования; дочерний тип должен стоять первым, а родительский тип — позже. Если мы этого не сделаем, это приведет к ошибке компиляции.

Третий способ — использовать блок multi-catch:

try {
    // ...
} catch (FileNotFoundException | EOFException ex) {
    // ...
}

Эта функция впервые появилась в Java 7; уменьшает дублирование кода и упрощает его обслуживание.

В5. В чем разница между проверенным и непроверенным исключением?

Проверяемое исключение должно быть обработано в блоке try-catch или объявлено в предложении throws; тогда как непроверенное исключение не требуется ни обрабатывать, ни объявлять.

Проверяемые и непроверяемые исключения также называются исключениями времени компиляции и выполнения соответственно.

Все исключения являются проверенными исключениями, кроме тех, на которые указывают Error, RuntimeException и их подклассы.

В6. В чем разница между исключением и ошибкой?

Исключение — это событие, представляющее собой состояние, из которого можно выйти, тогда как ошибка представляет собой внешнюю ситуацию, из которой обычно невозможно выйти.

Все ошибки, выдаваемые JVM, являются экземплярами Error или одного из его подклассов, наиболее распространенные из них включают, но не ограничиваются: , и сборщику мусора не удалось сделать более доступным StackOverflowError — возникает, когда закончилось пространство стека для потока, обычно из-за слишком глубокой рекурсии приложения. ExceptionInInitializerError — сигнализирует о том, что во время оценки статического инициализатор NoClassDefFoundError — возникает, когда загрузчик классов пытается загрузить определение класса и не может его найти, обычно потому, что требуемые файлы классов не были найдены в пути к классам. UnsupportedClassVersionError — возникает, когда JVM пытается прочитать класс. файл и определяет, что версия в файле не поддерживается, обычно потому, что файл был создан с более новой версией Java

    «Хотя ошибку можно обработать с помощью оператора try, это не рекомендуется, поскольку нет гарантии, что программа сможет что-либо надежно сделать после возникновения ошибки.

В7. Какое исключение будет выдано при выполнении следующего блока кода?

Выдает исключение ArrayIndexOutOfBoundsException, поскольку мы пытаемся получить доступ к позиции, превышающей длину массива.

Integer[][] ints = { { 1, 2, 3 }, { null }, { 7, 8, 9 } };
System.out.println("value = " + ints[1][1].intValue());

Q8. Что такое цепочка исключений?

Происходит, когда исключение генерируется в ответ на другое исключение. Это позволяет нам открыть полную историю поднятой нами проблемы:

Q9. Что такое трассировка стека и как она связана с исключением?

try {
    task.readConfigFile();
} catch (FileNotFoundException ex) {
    throw new TaskException("Could not perform task", ex);
}

Трассировка стека предоставляет имена классов и методов, которые были вызваны, от запуска приложения до момента возникновения исключения.

Это очень полезный инструмент отладки, так как он позволяет нам точно определить, где в приложении возникло исключение, и первоначальные причины, вызвавшие его.

Q10. Зачем вам подкласс исключения?

Если тип исключения не представлен теми, которые уже существуют на платформе Java, или если вам нужно предоставить дополнительную информацию клиентскому коду для более точной обработки, вам следует создать пользовательское исключение.

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

Кроме того, вы должны наследовать от наиболее конкретного подкласса Exception, который тесно связан с тем, который вы хотите сгенерировать. Если такого класса нет, то выберите Exception в качестве родителя.

В11. Каковы некоторые преимущества исключений?

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

Кроме того, поскольку JVM просматривает стек вызовов в обратном направлении, чтобы найти любые методы, заинтересованные в обработке определенного исключения; мы получаем возможность распространять ошибку вверх по стеку вызовов без написания дополнительного кода.

Кроме того, поскольку все исключения, создаваемые в программе, являются объектами, их можно группировать или классифицировать на основе иерархии классов. Это позволяет нам перехватывать группу исключений в одном обработчике исключений, указывая суперкласс исключения в блоке перехвата.

Q12. Можете ли вы создать какое-либо исключение внутри тела лямбда-выражения?

При использовании стандартного функционального интерфейса, уже предоставленного Java, вы можете генерировать только непроверенные исключения, потому что стандартные функциональные интерфейсы не имеют предложения «throws» в сигнатурах методов:

Однако, если вы используете настраиваемый функциональный интерфейс, возможно создание проверенных исключений:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {
    if (i == 0) {
        throw new IllegalArgumentException("Zero not allowed");
    }
    System.out.println(Math.PI / i);
});

@FunctionalInterface
public static interface CheckedFunction<T> {
    void apply(T t) throws Exception;
}
public void processTasks(
  List<Task> taks, CheckedFunction<Task> checkedFunction) {
    for (Task task : taks) {
        try {
            checkedFunction.apply(task);
        } catch (Exception e) {
            // ...
        }
    }
}

processTasks(taskList, t -> {
    // ...
    throw new Exception("Something happened");
});

Q13. Каким правилам нужно следовать при переопределении метода, выдающего исключение?

Несколько правил определяют, как должны объявляться исключения в контексте наследования.

Когда метод родительского класса не генерирует никаких исключений, метод дочернего класса не может генерировать никаких проверенных исключений, но может генерировать любые непроверенные исключения.

Вот пример кода, демонстрирующий это:

class Parent {
    void doSomething() {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IllegalArgumentException {
        // ...
    }
}

Следующий пример не скомпилируется, поскольку переопределяющий метод выдает проверенное исключение, не объявленное в переопределенном методе:

class Parent {
    void doSomething() {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IOException {
        // Compilation error
    }
}

Когда метод родительского класса генерирует одно или несколько проверенных исключений, метод дочернего класса может генерировать любое непроверенное исключение; все, никакие или подмножество объявленных проверенных исключений, и даже большее их количество, если они имеют ту же область действия или более узкую.

Вот пример кода, который успешно следует предыдущему правилу:

class Parent {
    void doSomething() throws IOException, ParseException {
        // ...
    }

    void doSomethingElse() throws IOException {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IOException {
        // ...
    }

    void doSomethingElse() throws FileNotFoundException, EOFException {
        // ...
    }
}

Обратите внимание, что оба метода соблюдают правило. Первый генерирует меньше исключений, чем переопределенный метод, а второй, хотя и генерирует больше; они уже по объему.

«Однако, если мы попытаемся сгенерировать проверенное исключение, которое метод родительского класса не объявляет, или мы сгенерируем исключение с более широкой областью действия; мы получим ошибку компиляции:

class Parent {
    void doSomething() throws FileNotFoundException {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IOException {
        // Compilation error
    }
}

Когда метод родительского класса имеет предложение throws с непроверяемым исключением, метод дочернего класса может не генерировать ни одного или любое количество непроверенных исключений, даже если они не связаны между собой.

Вот пример, который соответствует правилу:

class Parent {
    void doSomething() throws IllegalArgumentException {
        // ...
    }
}

class Child extends Parent {
    void doSomething()
      throws ArithmeticException, BufferOverflowException {
        // ...
    }
}

Q14. Будет ли компилироваться следующий код?

void doSomething() {
    // ...
    throw new RuntimeException(new Exception("Chained Exception"));
}

Да. При цепочке исключений компилятор заботится только о первом в цепочке, и, поскольку он обнаруживает непроверенное исключение, нам не нужно добавлять предложение throws.

Q15. Есть ли способ генерировать проверенное исключение из метода, который не имеет пункта Throws?

Да. Мы можем воспользоваться стиранием типов, выполняемым компилятором, и заставить его думать, что мы выбрасываем непроверенное исключение, когда на самом деле; мы выбрасываем проверенное исключение:

public <T extends Throwable> T sneakyThrow(Throwable ex) throws T {
    throw (T) ex;
}

public void methodWithoutThrows() {
    this.<RuntimeException>sneakyThrow(new Exception("Checked Exception"));
}

3. Заключение

В этой статье мы рассмотрели некоторые вопросы, которые могут возникнуть на технических собеседованиях с разработчиками Java, касающиеся исключений. Это не исчерпывающий список, и его следует рассматривать только как начало дальнейших исследований.

Мы в Baeldung желаем вам успехов в любых предстоящих интервью.

Next »

Java Annotations Interview Questions (+ Answers)

« Previous

Java Flow Control Interview Questions (+ Answers)