Исключения
Исключение – это проблема, которая возникает во время выполнения программы. Вызов исключения прерывает нормальный ход программы и программа завершается нестандартным способом. Не хотелось бы, чтобы пользователь получал большое количество системного кода в том случае, если он введет, например, буквы в поле, которое требует числовых значений.
Для того, чтобы предотвратить это, в языке программирования Java существует механизм обработки исключений.
Исключение может быть вызвано одним из таких процессов:
- Нехватка физической памяти
- Ввод пользователем некорректных данных
- Искомый файл не существует
- Потеря соединения с сетью и т.д.
Иерархия исключений в Java:
Методы класса Throwable
- public String getMessage() Возвращает детальное сообщение о вызванном исключении
- public Throwable getCause() Возвращает причину исключения
- public String toString() Возвращает имя класса и является частью результата метода getMessage()
- public void printStackTrace() Выводит результат метода toString() Вместе с цепочкой исключений в выходной поток System.err
- public StackTraceElement[] getStackTrace() Возвращает массив, который содержит каждый элемент цепочки исключения. 0 элемент массива является верхним элементом стека, а крайний – нижним
- public Throwable fillinStackTrace() Заполняет стек объекта Throwable текущим стеком исключения, добавляясь к предыдущей информации в стеке
Все исключения можно поделить на 3 большие группы:
- Проверяемые исключения
- Непроверяемые исключения
- Ошибки
Проверяемые исключения
Это исключения, которые появляются во время компиляции программы. Например, если мы используем класс FileReader в программе для чтения данных из файла, который не существует, то получим исключение FileNotFoundException и нам придется решать эту проблему.
Рассмотрим пример простого приложения:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class FileNotFoundEXceptionDemo {
public static void main(String[] args) throws IOException {
File file = new File("/home/proselyte/Programming/Projects/Proselyte/JavaCore/resources/nullFile.txt");
FileReader fileReader = new FileReader(file);
char[] charArray = new char[100];
fileReader.read(charArray);
for (char character : charArray) {
System.out.print(character);
}
fileReader.close();
}
}
В результате работы программы получим примерно следующий результат:
/*Some system messages*/
Exception in thread "main" java.io.FileNotFoundException: /home/proselyte/Programming/Projects/Proselyte/JavaCore/resources/nullFile.txt (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.(FileInputStream.java:146)
at java.io.FileReader.(FileReader.java:72)
at net.proselyte.javacore.exceptions.FileNotFoundEXceptionDemo.main(FileNotFoundEXceptionDemo.java:10)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Непроверяемые исключения
Это исключения, которые вызываются во время выполнения программы.
Например, у нас есть массив целых чисел на 10 элементов и мы пытаемся получить значение 11 элемента. Т.е. мы пытаемся залезть в участок физической памяти, содержимое которого нам неизвестно. JVM не даст нам этого сделать и выдаст исключение ArrayIndexOutOfBoundsException.
Рассмотрим, как это выглядит в приложении:
package net.proselyte.javacore.exceptions;
public class IndexOutOfBoundsExceptionDemo {
public static void main(String[] args) {
int[] integers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println("Trying to call 10th element of array: ");
System.out.println(integers[10]);
}
}
В результате работы программы получим следующий результат:
Trying to call 10th element of array:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at net.proselyte.javacore.exceptions.IndexOutOfBoundsExceptionDemo.main(IndexOutOfBoundsExceptionDemo.java:8)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Ошибки
Ошибки, как правило, игнорируются компилятором, потому что с его точки зрения все идет правильно. Другими словами, на момент компиляции все должно работать исправно. Но во время выполнения программы что-то происходит.
Например, в нашей программе случилась такая ситуация, при которой 2 метода рекурсивно вызывают друг друга. В результате мы столкнемся с тем, что у нас закончится выделенная память.
Для понимания того, как это работает на практике, рассмотрим пример простого приложения:
public class StackOverFlowErrorDemo {
public static void main(String[] args) {
methodA();
}
static public void methodA(){
methodB();
}
static public void methodB(){
methodA();
}
}
В результате работы программы получим следующий результат:
Exception in thread "main" java.lang.StackOverflowError
at net.proselyte.javacore.exceptions.StackOverFlowErrorDemo.methodA(StackOverFlowErrorDemo.java:9)
at net.proselyte.javacore.exceptions.StackOverFlowErrorDemo.methodB(StackOverFlowErrorDemo.java:13)
at net.proselyte.javacore.exceptions.StackOverFlowErrorDemo.methodA(StackOverFlowErrorDemo.java:9)
at net.proselyte.javacore.exceptions.StackOverFlowErrorDemo.methodB(StackOverFlowErrorDemo.java:13)
Как мы видим, оба эти метода используются в программе и не могут быть удалены сборщиком мусора. Поэтому, когда место в стеке JVM закончится, мы получим исключение StackOverflowError.
Обработка исключений
Используя комбинацию ключевых слов try и catch, существует возможность находить исключения, которые возникают во время работы программы. Внутри блока try/catch размещается код, который может вызвать исключение.
В программе это выглядит таким образом:
try{
//Code that can call an exception
} catch (SomeException ex){
//Sequence of actions in case of exception
}
Другими словами, мы "пытаемся" (try) выполнить фрагмент кода и “ловим” (catch) исключения, которые могут возникнуть.
Для понимания того, как это работает на практике, рассмотрим пример простого приложения.
Мы попробуем прочитать файл, который не существует. Обработаем исключение и посмотрим, что из этого выйдет:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class TryCatchDemo {
public static void main(String[] args) {
File file = new File("/home/proselyte/Programming/Projects/Proselyte/JavaCore/resources/nullFile.txt");
try {
FileReader fileReader = new FileReader(file);
char[] charArray = new char[100];
fileReader.read(charArray);
for (char character : charArray) {
System.out.print(character);
}
fileReader.close();
}catch (IOException e){
System.out.println("We have IOException here.");
}
}
}
В результате работы программы получим следующий результат:
/*Some system messages*/
We have IOException here.
Кроме этого, во время использования нескольких блоков catch, либо при указании нескольких исключений в одном блоке catch, может обрабатываться несколько исключений.
В коде это выглядит таким образом:
catch (IOException|FileNotFoundException ex) {
System.out.println("We have an exception here...");
throw ex;
Ключевые слова throw/throws
Если известно, что наш метод может вызвать исключение, то можно использовать ключевое слово throws с обозначением исключения, которое может быть вызвано.
Для того, чтобы “бросить” полученное / созданное исключение, мы используем ключевое слово throw.
Пример:
import java.io.*;
public class ThrowThrowsDemo{
public void calculateSum(int a, int b) throws SomeException {
//Some code
throw new SomeException();
}
}
Блок finally
Блок finally может быть частью блока try/catch, и код, находящийся в нем, выполняется в любом случае (только если не “положить” JVM).
Давайте посмотрим, как это работает на примере простого приложения:
import java.util.Arrays;
public class FinallyDemo {
public static void main(String[] args) {
int[] intArray = new int[10];
System.out.println("Filling our array");
for (int i = 0; i
<
intArray.length; i++) {
intArray[i] = i;
}
try {
System.out.println("Element number 11: " + intArray[10]);
} catch (ArrayIndexOutOfBoundsException ex) {
System.out.println("We have an exception here: " + ex);
} finally {
System.out.println("\n=================================================");
System.out.println("intArray: " + Arrays.toString(intArray));
System.out.println("=================================================\n");
}
}
}
В результате работы программы получим следующий результат:
/*Some system messages*/
Filling our array
We have an exception here: java.lang.ArrayIndexOutOfBoundsException: 10
=================================================
intArray: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
=================================================
Как мы видим: несмотря на исключение, вызванное попыткой получить доступ за пределами массива, в консоль выводится сам массив, так как он находится в блоке finally.
Блок try/catch с ресурсами
Использовать соединения, потоки и т.д. вместе с блоком try/catch.
Эта конструкция называется try-with-resources.
Рассмотрим пример простого приложения:
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main(String[] args) {
try (FileReader fileReader = new FileReader("resources/message.txt")) {
char[] charArray = new char[100];
fileReader.read(charArray);
for (char character : charArray) {
System.out.print(character);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
В результате работы программы получим следующий результат:
/*Some system messages*/
This is test message for TryWithResources class.
Создание собственных исключений
При разработке больших приложений, мы часто сталкиваемся с необходимостью создания собственных исключений.
Вот условия их создания:
- Исключение должно быть наследником класса Throwable
- Для создания проверяемого исключения мы должны наследоваться от класса Exception.
- Для создания непроверяемого исключения мы должны наследоваться от класса RuntieException
Пример.
Класс NegativeWidthException:
public class NegativeWidthException extends Exception{
private int width;
public NegativeWidthException(int width){
this.width = width;
}
public int getWidth() {
return width;
}
}
Класс Square:
public class Square {
private int width;
public Square(int width) {
this.width = width;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int calculateArea(int width) throws NegativeWidthException {
if (width
>
= 0) {
return width * width;
}else {
throw new NegativeWidthException(width);
}
}
}
Класс NegativeWidthExceptionDemo:
public class NegativeWidthExceptionDemo {
public static void main(String[] args)throws NegativeWidthException{
Square square = new Square(-1);
System.out.println("Square width: " + square.getWidth());
System.out.printf("Calculating area...\n");
System.out.println("Area: " + square.calculateArea(square.getWidth()));
}
}
В результате работы программы получим следующий результат:
/*Some system messages*/
Exception in thread "main" Square width: -1
Calculating area...
net.proselyte.javacore.exceptions.NegativeWidthException
at net.proselyte.javacore.exceptions.Square.calculateArea(Square.java:22)
at net.proselyte.javacore.exceptions.NegativeWidthExceptionDemo.main(NegativeWidthExceptionDemo.java:10)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
В этой программе мы пытаемся вычислить площадь квадрата, но проверяем значение длины стороны этого квадрата. Если значение отрицательное, то мы “бросаем” наше исключение.