Программирование

Как правильно обрабатывать исключения Java

Как правильно обрабатывать исключения Java

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

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

Понимание исключений Java

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

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

Помните, что исключение — это просто объект, но с одной важной характеристикой: оно должно быть расширено из класса Exception или любого подкласса Exception . Хотя в Java есть все виды встроенных исключений, вы также можете создавать свои собственные, если хотите. Некоторые из наиболее распространенных исключений Java включают в себя:

  • NullPointerException
  • NumberFormatException
  • IllegalArgumentException
  • RuntimeException
  • IllegalStateException

Так что же происходит, когда вы бросаете исключение?

Во-первых, Java просматривает непосредственный метод, чтобы увидеть, есть ли код, который обрабатывает исключение, которое вы бросили. Если обработчик не существует, он смотрит на метод, который вызвал текущий метод, чтобы увидеть, существует ли там дескриптор. Если нет, то он смотрит на метод, вызвавший этот метод, а затем на следующий метод и т. Д. Если исключение не перехватывается, приложение печатает трассировку стека, а затем вылетает. (На самом деле это больше нюанс, чем просто сбой, но это сложная тема, выходящая за рамки этой статьи.)

Трассировка стека — это список всех методов, которые Java обошла при поиске обработчика исключений. Вот как выглядит трассировка стека:

 Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) 

Мы можем многое почерпнуть из этого. Во-первых, выброшенное исключение было исключением NullPointerException . Это произошло в getTitle() в строке 16 Book.java. Этот метод был вызван из getBookTitles() в строке 25 Author.java. Этот метод был вызван из main() в строке 14 Bootstrap.java. Как видите, знание всего этого облегчает отладку.

Но опять же, истинное преимущество исключений состоит в том, что вы можете «обрабатывать» ненормальное состояние, перехватывая исключение, исправляя ситуацию и возобновляя приложение без сбоев.

Использование исключений Java в коде

Допустим, у вас есть someMethod() который принимает целое число и выполняет некоторую логику, которая может сломаться, если целое число меньше 0 или больше 100. Это может быть хорошим местом для создания исключения:

 public void someMethod(int value) { if (value < 0 || value > 100) { throw new IllegalArgumentException (); } // ... } 

Чтобы поймать это исключение, вам нужно перейти туда, где вызывается someMethod() и использовать блок try-catch :

 public void callingMethod() { try { someMethod(200); someOtherMethod(); } catch (IllegalArgumentException e) { // handle the exception in here } // ... } 

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

В нашем примере мы входим в блок try и немедленно вызываем someMethod() . Поскольку 200 не находится между 0 и 100, создается IllegalArgumentException . Это немедленно завершает выполнение someMethod() , пропускает остальную логику в блоке try ( someOtherMethod() никогда не вызывается) и возобновляет выполнение в блоке catch.

Что произойдет, если мы вместо этого someMethod(50) ? IllegalArgumentException никогда не будет выброшено. someMethod() будет выполняться как обычно. Блок try будет выполняться как обычно, вызывая someOtherMethod() после завершения someMethod (). Когда someOtherMethod() завершается, блок catch будет пропущен, и callingMethod() будет продолжен.

Обратите внимание, что вы можете иметь несколько блоков catch на каждый блок try:

 public void callingMethod() { try { someMethod(200); someOtherMethod(); } catch (IllegalArgumentException e) { // handle the exception in here } catch (NullPointerException e) { // handle the exception in here } // ... } 

Также обратите внимание, что необязательный блок finally также существует:

 public void method() { try { // ... } catch (Exception e) { // ... } finally { // ... } } 

Код внутри блока finally всегда выполняется независимо от того, что. Если у вас есть оператор return в блоке try, блок finally выполняется перед возвратом из метода. Если вы выбросите другое исключение в блоке catch, блок finally выполняется до того, как будет сгенерировано исключение.

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

Обратите внимание, что вы можете иметь блок finally без блока catch:

 public void method() { try { // ... } finally { // ... } } 

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

Проверенные и непроверенные исключения в Java

В отличие от большинства языков, Java различает проверенные исключения и непроверенные исключения (например, C # имеет только непроверенные исключения). Проверенное исключение должно быть перехвачено в методе, в котором генерируется исключение, иначе код не будет компилироваться.

Чтобы создать проверенное исключение, пройдите от Exception . Чтобы создать непроверенное исключение, расширьте RuntimeException .

Любой метод, который выбрасывает проверенное исключение, должен обозначать это в сигнатуре метода, используя ключевое слово throws . Поскольку встроенное в Java исключение IOException является проверенным, следующий код не будет компилироваться:

 public void wontCompile() { // ... if (someCondition) { throw new IOException(); } // ... } 

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

 public void willCompile() throws IOException { // ... if (someCondition) { throw new IOException(); } // ... } 

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

Когда следует использовать отмеченные или непроверенные исключения?

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

Но это руководство может быть устаревшим. С одной стороны, проверенные исключения приводят к более надежному коду. С другой стороны, ни один другой язык не проверял исключения так же, как Java, что показывает две вещи: во-первых, эта функция недостаточно полезна для других языков, чтобы ее украсть, и во-вторых, вы можете абсолютно без них жить. Кроме того, проверенные исключения не очень хорошо работают с лямбда-выражениями, представленными в Java 8.

Рекомендации по использованию исключений Java

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

  • Предпочитаю конкретные исключения общим исключениям. Используйте NumberFormatException поверх IllegalArgumentException когда это возможно, в противном случае используйте IllegalArgumentException поверх RuntimeException когда это возможно.
  • Никогда не ловить Throwable ! Класс Exception фактически расширяет Throwable , а блок catch фактически работает с Throwable или любым классом, расширяющим Throwable. Однако класс Error также расширяет Throwable , и вы никогда не захотите отлавливать Error поскольку Error указывают на серьезные неисправимые проблемы.
  • Никогда не лови Exception ! InterruptedException расширяет Exception , поэтому любой блок, который перехватывает Exception , также перехватывает InterruptedException , и это очень важное исключение, с которым вы не хотите связываться (особенно в многопоточных приложениях), если не знаете, что делаете. Если вы не знаете, какое исключение нужно ловить, подумайте о том, чтобы ничего не перехватить.
  • Используйте описательные сообщения для облегчения отладки. Когда вы генерируете исключение, вы можете предоставить String сообщение в качестве аргумента. К этому сообщению можно получить доступ в блоке catch с помощью метода Exception.getMessage() , но если исключение никогда не перехватывается, сообщение также будет отображаться как часть трассировки стека.
  • Старайтесь не ловить и игнорировать исключения. Чтобы обойти неудобство проверенных исключений, многие новички и ленивые программисты установят блок catch, но оставят его пустым. Плохо! Всегда обрабатывайте это изящно, но если вы не можете, по крайней мере распечатайте трассировку стека, чтобы вы знали, что было сгенерировано исключение. Вы можете сделать это, используя метод Exception.printStackTrace() .
  • Остерегайтесь чрезмерного использования исключений. Когда у тебя есть молоток, все выглядит как гвоздь. Когда вы впервые узнаете об исключениях, вы можете почувствовать себя обязанным превратить все в исключение… до такой степени, что большая часть потока управления вашего приложения сводится к обработке исключений. Помните, исключения предназначены для «исключительных» случаев!

Теперь вы должны чувствовать себя достаточно комфортно с исключениями, чтобы понять, что это такое, почему они используются и как включить их в свой собственный код. Если вы не до конца понимаете концепцию, это нормально! Мне потребовалось некоторое время, чтобы это «щелкнуло» в моей голове, так что не думайте, что вам нужно торопиться. Не торопитесь.

Есть вопросы? Знаете какие-либо другие советы, связанные с исключениями, которые я пропустил? Поделитесь ими в комментариях ниже!

Похожие посты
Программирование

Что такое канал RSS? (И где его взять)

Программирование

7 причин, почему изображения не загружаются на ваш сайт

Программирование

Запустите агент SQL Server: настройте SQL Server 2012

Программирование

15 лучших бесплатных обоев дня Святого Патрика