Java. Виключення (excetions). Виключна ситуація. Ключові слова try, catch, finally. Приклади

Виключення (excetions). Виключна ситуація. Ключові слова try, catch, finally. Приклади


Зміст


1. Що таке виключна ситуація?

Виключна ситуація – це програмна помилка, яка виникає під час виконання послідовності програмного коду. Програмна помилка може бути логічною помилкою програміста під час розробки програми. Наприклад, виключна ситуація може виникати у випадку:

  • спроби ділення на нуль
  • спроби звернення до елементу масиву, номер індексу якого виходить за межі оголошеного;
  • спроби взяти корінь з від’ємного числа;
  • спроби відкрити файл за іменем, якого немає на диску;
  • інші випадки.

Правильна обробка виключень є важливим елементом написання професійних програм на Java.

 

2. Поняття виключення в мові Java

Як правило, кожна виключна ситуація має мати свій код помилки та обробку цього коду (виведення відповідних повідомлень, тощо). У мові програмування Java розроблено механізм обробки виключних ситуацій.

У мові програмування Java, виключення – це спеціальний об’єкт, який описує виключну ситуацію, що виникла в деякій частині програмного коду. Об’єкт, що представляє виключення, генерується в момент виникнення виключної ситуації. Після виникнення критичної (виключної) ситуації виключення перехоплюється та обробляється. Таким чином, виникає поняття генерування виключення. Генерування виключення – процес створення об’єкту, що описує дане виключення.

 

3. Якими способами можуть генеруватись виключення?

У мові Java виключення можуть генеруватись одним з двох способів:

  • автоматично. У цьому випадку виключення генеруються виконавчою системою Java. До таких виключень входять помилки, що порушують правила мови Java чи обмеження, що накладуються системою;
  • вручну. У цьому випадку виключення генеруються в програмному коді, який розробляється. Ручні виключення програмуються для повідомлення викликаючому коду про можливі помилки в методах розроблюваної програми.

 

4. В яких випадках викликається стандартний обробник виключень Java?

Стандартний обробник виключень Java викликається у випадках, коли програма:

  • не використовує блок try…catch для обробки та перехоплення виключної ситуації взагалі;
  • містить блок try…catch, однак у цьому блоці даний тип виключення не перехоплюється.

Якщо програма містить власний код try…catch для обробки даної виключної ситуації, тоді стандартний обробник виключень не викликається.

 



5. Які ключові слова використовуються для обробки виключень в Java?

Для обробки виключень в Java використовуються наступні ключові слова:

  • try;
  • catch;
  • throw;
  • throws;
  • finally.

 

6. Яке призначення конструкції try… catch…finally? Загальна форма

У мові Java можна вручну перехоплювати виключення (виключні ситуації) та відповідним чином обробляти їх. Важливо, щоб при виникненні виключної ситуації програма не припинила своє виконання.

Для відслідковування, генерування та обробки виключень у мові програмування Java використовується конструкція try…catch…finally, яка має таку загальну форму

try {
    // блок коду, в якому відслідковуються та генеруються виключення
    // ...
}
catch (type_exception_1 objEx1) {
    // обробник виключення типу type_exception_1
    // ...
}
catch (type_exception_2 objEx2) {
    // обробник виключення типу type_exception_2
    // ...
}
...
catch (type_exception_N objExN) {
    // обробник виключення типу type_exception_N
    // ...
}
finally {
    // блок коду, що має бути обов’язково виконаний
    // після завершення блоку try
    // ...
}

У блоці try розміщуються оператори програми, які потрібно проконтролювати та у випадку виникнення виключної ситуації згенерувати виключення. Всередині блоку try могуть викликатись різні методи, здатні згенерувати те чи інше виключення. Однак, обробник виключення буде тільки один.

У блоці catch розміщується програмний код, який обробляє перехоплене виключення (обробник виключення). Код у блоці catch реалізує виконання відповідних дій, якщо відбулась виключна ситуація у блоці try.   Блоків catch може бути декілька. Якщо генерується виключення, механізм обробки виключень шукає перший з обробників виключень, аргумент якого відповідає поточному типу виключення. Після цього він входить у блок catch, і, в результаті виключення вважається обробленим. Тільки один відповідний блок catch обробляється.

У блоці finally вказується код, який повинен бути обов’язково виконаний після завершення блоку try. Блок операторів finally виконується незалежно від того, чи буде згенероване виключення чи ні. Якщо виключення згенероване, блок операторів finally виконується навіть при умові, що жоден з операторів catch не співпадає з цим виключенням.

Оператори try і catch складають єдине ціле. Оператор finally може бути відсутній.

 

7. Приклад генерування виключення та його перехоплення стандартним обробником виключень Java

Якщо у програмі відсутній власний блок try…catch перехоплення виключення, викликається стандартний обробник виключення. У прикладі демонструється виклик стандартного обробника.

Оголошується клас DemoExceptions. З метою демонстрації у класі оголошується метод DivNumbers(). Метод повертає число типу double, що є результатом ділення двох цілих значень змінних a та b. Ці змінні є вхідними параметрами методу.

Якщо b = 0 то при діленні a на b може виникнути виключна ситуація “ділення на нуль” типу ArithmeticException. У програмі немає коду, який явно перехоплює та обробляє цю ситуацію. Тому, викликається стандартний обробник Java.

Текст класу DemoExceptions наступний:

// клас, що реалізує метод,
// в якому може виникнути виключна ситуація
class DemoExceptions {
    // метод, в якому може виникнути ділення на 0
    double DivNumbers(int a, int b) {
        // при діленні на 0 викликається стандартний обробник Java
        double res;
        res = a/b;
        return res;
    }
}

Якщо в іншому класі використати метод DivNumbers() наступним чином

public class Train01 {

    public static void main(String[] args) {
        DemoExceptions dE = new DemoExceptions(); // створити екземпляр класу
        double res;

        // викликати метод DivNumbers()
        res = dE.DivNumbers(2, 0); // 2/0 => ділення на нуль
        System.out.println("res = " + res);
    }
}

то буде викликаний стандартний обробник Java, який виведе наступне повідомлення

Exception in thread "main" java.lang.ArithmeticException: / by zero
at DemoExceptions.DivNumbers(Train01.java:10)
at Train01.main(Train01.java:82)

У повідомленні вказується тип класу ArithmeticException. Це є підклас, що є похідним від класу RunTimeException. Цей підклас описує арифметичні помилки типу “ділення на 0”, “взяття кореня з від’ємного числа” тощо.

 

8. Приклад перехоплення та обробки виключення з допомогою оператора try…catch

У прикладі реалізовано клас DemoExceptions, в якому оголошується метод з іменем DivNumbers2(). Метод DivNumbers2() ділить вхідний параметр a на вхідний параметр b. У методі DivNumbers2() продемонстровано перехоплення виключення ділення на нуль з допомогою блоку try…catch.

Текст класу DemoExceptions наступний

// клас, що реалізує метод,
// в якому може виникнути виключна ситуація
class DemoExceptions {
    // метод, в якому оброблено ділення на 0,
    // блок try..catch
    double DivNumbers2(int a, int b) {
        double res=0; // змінна res обов'язково має бути ініціалізована
        try {
            res = a/b; // якщо b=0, то генерується виключення
        }
        catch (ArithmeticException e) {
            System.out.println("Ділення на 0.");
        }

        return res;
    }
}

Тепер, при використанні методу DivNumbers2() в іншому класі

public class Train01 {

    public static void main(String[] args) {
        DemoExceptions dE = new DemoExceptions(); // створити екземпляр класу
        double res;

        // викликати метод DivNumbers2()
        res = dE.DivNumbers2(2, 0); // 2/0 => ділення на нуль
        System.out.println("res = " + res);
    }
}

система видасть наступний результат після запуску програми на виконання

Ділення на 0.
res = 0.0

Як видно з вищенаведеного коду, в методі DivNumbers2() викликається власний обробник, який реалізований в операторі try…catch. У блок try поміщається програмний код, який потрібно перевірити

...
res = a/b;
...

Якщо виникає виключення (b=0), то управління одразу передається з блоку try в блок catch. У блоці catch відбувається обробка виключення з виведенням відповідного повідомлення

...

catch (ArithmeticException e) {
    System.out.println("Ділення на 0.");
}

...

Отже, якщо b=0:

  • генерується виключення типу ArithmeticException;
  • відбуваєтсья перехід до обробки виключення (блок catch);
  • у блоці catch виводиться відповідне повідомлення;
  • після блоку catch виконання програми не зупиняється. У результаті змінна res отримує значення 0.

Можна змінити вивід власного повідомлення про виключення на стандартне повідомлення. У цьому випадку потрібно змінити текст в блоці catch на наступний

...

catch (ArithmeticException e) {
    System.out.println(e);
    //System.out.println("Ділення на 0.");
}

...

Після змін, результат виконання програми буде такий

java.lang.ArithmeticException: / by zero
res = 0.0

 

9. Приклад перехоплення та обробки виключення блоком try…catch…finally. Демонстрація роботи блоку операторів finally

У прикладі продовжено розвиток двох попередніх пунктів 7, 8. Приклад демонструє призначення блоку finally в операторі try…catch…finally.

Оголошується клас DemoExceptions. У класі продемонстровано перехоплення виключної ситуації ділення на 0. Це перехоплення реалізоване в методі DivNumbers3() класу. Для перехоплення та обробки виключної ситуації, метод DivNumbers3() використовує оператор try…catch…finally.

Текст класу DemoExceptions наступний

// клас, що реалізує метод,
// в якому може виникнути виключна ситуація
class DemoExceptions {
    // метод, в якому оброблено ділення на 0,
    // блок try..catch..finally
    double DivNumbers3(int a, int b) {
        double res; // не обов'язково ініціалізувати, тому що є блок finally
        try {
            res = a/b;
        }
        catch (ArithmeticException e) {
            System.out.println("Ділення на 0.");
        }
        finally {
            res = 0; // викликається обов'язково
        }
        return res;
    }
}

У методі DivNumbers3() оголошується змінна res, що є результатом ділення a на b. Оскільки оператор try…catch містить блок finally, то рядок

...

finally {
    res = 0; // викликається обов'язково
}

...

буде викликатись завжди. І перед оператором

...

return res;

...

змінна res буде завжди ініціалізована. Тому компілятор Java не вимагає ініціалізації змінної на початку коду. Як відомо, що повернути значення змінної оператором return, ця змінна має бути обов’язково ініціалізована. Отже, блок finally викликається завжди.

Використання методу DivNumbers3() в іншому класі може бути таким

public class Train01 {

    public static void main(String[] args) {
        DemoExceptions dE = new DemoExceptions();
        double res;
        res = dE.DivNumbers3(2, 0); // 2/0 => ділення на нуль
        System.out.println("res = " + res);
    }
}

Результат виконання програми

Ділення на 0.
res = 0.0

 

10. Як реалізувати вивід опису виключення? Приклад

Якщо потрібно вивести опис виключення e, то можна змінити вивід в обробнику catch наступним чином (див. пункт 8)

...

catch (ArithmeticException e) {
    // перехопити помилку ділення на 0
    System.out.println("Виключення: " + e);
}

...

У реультаті програма буде виводити наступний результат

Виключення: java.lang.ArithmeticException: / by zero

 

11. Приклад перехоплення декількох виключних ситуацій (декілька операторів catch)

Бувають випадки, що в одному фрагменті коду може виникнути декілька виключень. Для обробки декількох виключних ситуацій у блоці try…cathch можна використовувати декілька операторів catch. Кожен з операторів catch використовується для перехоплення окремого типу виключення.

Реалізація класу DivArrays наступна

// декілька операторів catch
// клас, що ділить поелементно масиви
class DivArrays {
    double[] A1;
    double[] A2;

    // конструктор класу
    DivArrays(int n1, double[] _A1, int n2, double[] _A2) {
        // створити масиви
        A1 = new double[n1];
        for (int i=0; i<n1; i++)
            A1[i] = _A1[i];

        A2 = new double[n2];
        for (int i=0; i<n2; i++)
            A2[i] = _A2[i];
    }

    // метод, що ділить поелементно масив A1 на масив A2
    // метод повертає масив типу double
    double[] Division() {
        double[] A3;
        int n3;

        // встановити найбільший з розмірів
        if (A1.length > A2.length)
            n3 = A1.length;
        else
            n3 = A2.length;

        A3 = new double[n3];

        // цикл по i, у циклі обробляється виключна ситуація
        for (int i=0; i<n3; i++) {
            try {
                // згенерувати виключення, якщо ділення на 0
                if (A2[i]==0.0)
                    throw new ArithmeticException();
                A3[i] = A1[i]/A2[i];
            }
            catch (ArithmeticException e) {
                // перехопити ділення на 0
                A3[i] = 0; // просто встановити в A3 значення 0
            }
            catch (ArrayIndexOutOfBoundsException e) {
                // перехопити вихід індексу за межі масиву
                // це випадок, коли довжини масивів відрізняються
                A3[i] = -1;
            }
        }

        return A3;
    }
}

Використання класу в іншому програмному коді

public class Train03 {

    public static void main(String[] args) {
        double[] A1 = { 2.5, 2.0, 1.5, 0.5 };
        double[] A2 = { 1.0, 0.0, 2.0 };
        double[] A3;

        DivArrays dA = new DivArrays(4,A1,3,A2);

        A3 = dA.Division();

        // вивести масив dA
        for (int i=0; i<A3.length; i++)
            System.out.println("A[" + i + "] = " + A3[i]);
    }
}

Результат виконання програми

A[0] = 2.5
A[1] = 0.0
A[2] = 0.75
A[3] = -1.0

 

 


Зв’язані теми