Вопросы об интервью с Java-исключениями (ответы)

Интервью по вопросам исключений в Java (+ ответы)

1. обзор

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

2. Вопросы

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

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

Q2. Какова цель ключевых слов "бросок" и "бросок"?

Ключевое слово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; уменьшает дублирование кода и облегчает обслуживание.

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

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

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

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

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

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

Все ошибки, выдаваемые JVM, являются экземплярамиError или одного из его подклассов, наиболее распространенные из них включают, но не ограничиваются:

  • OutOfMemoryError - выбрасывается, когда JVM не может выделить больше объектов, потому что не хватает памяти, и сборщик мусора не может сделать больше доступных

  • StackOverflowError - возникает, когда пространство стека для потока исчерпано, обычно из-за слишком глубокой рекурсии приложения.

  • ExceptionInInitializerError - сигнализирует, что во время оценки статического инициализатора произошло непредвиденное исключение

  • NoClassDefFoundError - выдается, когда загрузчик классов пытается загрузить определение класса и не может его найти, обычно потому, что требуемые файлыclass не были найдены в пути к классам

  • UnsupportedClassVersionError - возникает, когда JVM пытается прочитать файлclass и определяет, что версия в файле не поддерживается, обычно потому, что файл был создан с помощью более новой версии Java

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

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

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

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

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

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

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

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

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

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

Q10. Почему вы хотите создать подкласс исключения?

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

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

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

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

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

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

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

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

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

List 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 {
    void apply(T t) throws Exception;
}
public void processTasks(
  List taks, CheckedFunction 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"));
}

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

Q15. Есть ли способ выбросить проверенное исключение из метода, не имеющего предложения о выбросах?

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

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

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

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

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

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