Исключение – это проблема, которая возникает во время выполнения программы. Вызов исключения прерывает нормальный ход программы, и программа завершается не нормальным способом. Нам бы не хотелось, чтобы пользователь получал кучу системного кода, в том случае, если он введёт, например буквы в поле, которое требует числовых значений.
Для того чтобы предотвратить это, в языке программирования 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 текущим стеком исключения, добавляясь к предыдущей информации в стеке.
Все исключения мы можем поделить на 2 большие группы:
- Проверяемые исключения
- Непроверяемые исключения
- Ошибки
Проверяемые исключения
Это исключения, которые появляются во время компиляции программы. Например, если мы используем класс 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.
- Для создания непроверяемого исключения, мы должны наследоваться от класса RuntimeException
Рассмотрим пример, приведённый ниже:
Пример:
Класс 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)
В этой программе мы пытаемся вычислить площадь квадрата, но проверяем значение длины стороны этого квадрата. Если значение отрицательное, то мы “бросаем” наше исключение.
В это уроке мы изучили основы исключений в языке программирования Java и рассмотрели примеры приложений с их использованием.
В следующем уроке мы более глубоко изучим такой базовый принцип ООП как наследование.