Страницы

Поиск по вопросам

понедельник, 2 декабря 2019 г.

Исключения и странное наследование

#java


Всем привет! Заметил такую вещь:


Как все мы прекрасно знаем существует 2 вида Exception:


Checked
Unchecked


Вопрос: Почему RuntimeException (unchecked), наследуется от класса Exception, который
является (checked)?

UPGRADE:   Исключения необходимо явно объявлять с помощью оператора throws,  объявляются
все исключения, кроме RuntimeException и Error потому что они UNCHECKED? Как понять
явно объявлять?
    


Ответы

Ответ 1



Короткий ответ Для того, чтобы конструкция: catch(Exception e) { ... } отлавливала и RuntimeException. Длинный ответ @Grundy уже объяснил, что RuntimeException явно прописан в спецификации и то, что данная иерархия была выбрана авторами Java и только они смогут достоверно объяснить, почему был выбран именно такой вариант, а не какой-либо другой. Я попробую объяснить почему выбранная иерархия имеет смысл. Error и Exception Начнем издалека. Есть базовый класс Throwable. Все методы, связанные с выбрасыванием исключений, определены в нем. Его наследники, что Error, что Exception, не объявляют никаких специфичных методов. По сути это один и тот же класс продублированный с разными названиями. Такая иерархия явно отделяет фатальные системные ошибки (Error) от нефатальных исключений (Exception). В теории, разработчик должен обрабатывать только исключения, а при возникновении ошибок, программа должна прекращать работу. Например, такой блок try: try { //какой-то код } catch(Exception e) { //обрабатываем исключение } Нормально обработает NullPointerException и продолжит работу. Но если возникнет OutOfMemoryError, то исполнение прекратится и ошибка будет выброшена на уровень выше. Проверяемые исключения Разработчики Java приняли решение, довольно спорное, что код должен явно обрабатывать все исключения, которые могут возникнуть. Т.е. если код обращается к методу, который может выбросить исключение, то его нужно либо явно обработать с помощью блока try-catch: try { //метод, который может выбросить IOException throwingMethod(); } catch(IOException e) { //явно обрабатывается } либо явно объявить в вызывающем методе с помощью throws: //Так мы объявляем всему миру, что наш метод опасный //и может выбросить исключение. //Любой код, который вызовет метод, должен будет либо //обработать исключение, либо передать дальше. void myMethod() throws IOException { throwingMethod(); } Если не сделать ни того, ни другого, то код не скомпилируется. Error — не проверяется, т.к., во-первых, Error не имеет смысла обрабатывать в большинстве случаев, и, во-вторых, ошибки обычно не относятся к какому-то конкретному методу (переполнение памяти может возникнуть в любой момент). Непроверяемые исключения Отдельные виды исключений потенциально очень широко распространены. Например, NullPointerException потенциально может возникнуть при вызове почти любого нестатичного метода: void method(MyClass obj) { //здесь может быть NPE obj.method(); //и здесь может быть NPE obj.getField().method(); //и здесь showMessage(obj.toString()); } Явная обработка NullPointerException привела бы к очень неудобному коду, с огромным количеством try catch и throws. Поэтому для таких исключений пришли к компромису и объявили класс RuntimeException. (Вопрос о том, какие исключения должны наследоваться от RuntimeException — довольно спорный. Есть рекомендации использовать непроверяемые исключения для программных ошибок, но на практике выбор может оказаться сложным) RuntimeException наследуется от Exception потому-что непроверяемое исключение это все еще исключение, а не ошибка: его можно обработать и оно всегда зависит от кода, а не от сторонних, системных, факторов. И если разработчик решит обрабатывать все исключения: catch(Exception e) { //обработка } , то должны обрабатываться и возникшие RuntimeException. Можно ли было сделать по-другому? Да, но есть нюансы. Рассмотрим варианты. Вариант 1. Разные классы-наследники для проверяемых и непроверяемых исключений. Можно было бы создать отдельные базовые классы для проверяемых и непроверяемых исключений: Exception RuntimeException CheckedException Но в этом случае возникает вопрос: должен ли быть проверяемым Exception и его наследники? Если да, то класс CheckedException теряет смысл и получаем существующую иерархию. Если нет, то теряет смысл RuntimeException. Тогда получаем второй вариант Вариант 2. Проверяемые исключения наследуются от непроверяемых Exception (unchecked) CheckedException (checked) Такая иерархия вполне допустима, но разработчики Java решили, что базовый класс должен быть проверяемым. Это соответствует выбранной философии: исключения должны быть проверяемыми по-умолчанию и непроверяемыми только если их повсеместная проверка вызывает затруднения. С этой философией не все согласны, но имеем, что имеем. Итог Почему RuntimeException (unchecked), наследуется от класса Exception, который является (checked)? По следующим причинам: Проверяемость тех или иных классов явно прописана в спецификации и не связана с какими-либо свойствами классов-исключений. Наследование имеет чисто организационный/иллюстративный характер и не связано с наследованием свойств/методов. Проверяемое исключение — это исключение, а не ошибка, и должно обрабатываться как исключение. Разработчики Java приняли решение, что базовый класс исключений должен быть проверяемым.

Ответ 2



Это явно определено в спецификации, section 11.1.1: RuntimeException and all its subclasses are, collectively, the runtime exception classes. The unchecked exception classes are the runtime exception classes and the error classes. The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are all subclasses of Throwable other than RuntimeException and its subclasses and Error and its subclasses. Выше в спецификации явно указывается, что checked exceptions – это все подклассы Throwable за исключением RuntimeException, Error и их подклассов. То есть RuntimeException и Error – это просто особые случаи для компилятора. Перевод ответа @JonSkeet

Ответ 3



RuntimeException и Error можно не проверять явно, т.е. они не должны быть обёрнуты в try-catch или методы не должны содержать throws для компиляции, в то время как обычные Exception обязаны обрабатываться с помощью try-catch или передаваться через throws. void neverCompiled(){ throw new Exception(); } void compiled(){ try{ throw new Exception(); } catch(Exception e){} } void compiled(){ throw new RuntimeException(); } class Complex { void compiled1() throws Exception { throw new Exception(); } void compiled2() { try{ compiled1(); } catch(Exception e){} } }

Комментариев нет:

Отправить комментарий