JavaRush /Java блог /Random UA /Винятки в Java (Java Exception)

Винятки в Java (Java Exception)

Стаття з групи Random UA
У повсякденному житті іноді виникають ситуації, що ми не планували. Наприклад, встаєш вранці на роботу, шукаєш зарядний пристрій до телефону, а його немає. Ідеш у ванну, щоб вмитися – відключабо воду. Сів у машину – не заводиться. Але людина може легко справитися з такими непередбаченими ситуаціями. А як із ними справляються Java-програми, постараємося розібратися у цій статті.

Що таке винятки (exceptions java)

У світі програмування виникнення помилок та непередбачених ситуацій під час виконання програми називають винятком (exception). У програмі виключення можуть виникати внаслідок неправильних дій користувача, відсутності ресурсу на диску, або втрати з'єднання з сервером по мережі. Причинами винятків при виконанні програми можуть бути помилки програмування або неправильне використання API. На відміну від нашого світу, програма має чітко знати, як чинити в такій ситуації. Для цього Java передбачено механізм винятків.

Коротко про ключові слова try, catch, finally, throws

Обробка виключень у Java базується на використанні в програмі таких ключових слів:
  • try – визначає блок коду, у якому може статися виняток;
  • catch - визначає блок коду, в якому відбувається обробка виключення;
  • finally – визначає блок коду, що є необов'язковим, але за його наявності виконується у разі незалежно від результатів виконання блоку try.
Ці ключові слова використовуються для створення в програмному коді спеціальних обробних конструкцій: try{}catch, try{}catch{}finally, try{}finally{}.
  • throw - використовується для порушення виключення;
  • throws - використовується в сигнатурі методів для попередження, що метод може викинути виняток.
Приклад використання ключових слів у програмі Java:
//Метод зчитує рядок з клавіатури

public String input() throws MyException {//попереджаємо за допомогою throws,
// що метод може викинути виняток MyException
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String s = null;
//В блок try укладаємо код, в якому може статися виняток, в даному
// у випадку компілятор нам підказує, що метод readLine() класу
// BufferedReader може викинути виняток введення/виводу
    try {
        s = reader.readLine();
// До блоку catch укладаємо код з обробки виключення IOException
    } catch (IOException e) {
        System.out.println(e.getMessage());
// у блоці finally закриваємо потік читання
    } finally {
// при закритті потоку теж можливий виняток, наприклад, якщо він не був відкритий, тому "обертаємо" код у блок try
        try {
            reader.close();
// пишемо обробку виключення при закритті потоку читання
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    if (s.equals("")) {
// ми вирішабо, що порожній рядок може порушити надалі роботу нашої програми, наприклад, в результаті цього методу нам треба викликати метод substring(1,2), тому ми змушені перервати виконання програми з генерацією свого типу виключення MyException за допомогою throw
        throw new MyException("String can not be empty!");
    }
    return s;
}

Навіщо потрібен механізм винятків?

Подивимося на приклад із реального світу. Уявіть, що автомобільна дорога має ділянку з аварійним мостом з обмеженою вантажопідйомністю. Якщо по ньому поїде автомобіль із масою, що перевищує вантажопідйомність моста, він може зруйнуватися, і ситуація для водія може стати, м'яко кажучи, винятковою. Щоб цього не сталося, дорожня служба завчасно встановлює попереджувальні знаки на дорозі. Водій автомобіля, дивлячись на попереджувальний знак, порівнюватиме масу свого автомобіля з дозволеною для проїзду мостом. Якщо вона перевищує її – він поїде об'їзною колією. Завдяки діям дорожньої служби водії вантажного транспорту, по-перше, отримали можливість заздалегідь змінювати свій шлях, по-друге, попереджені про небезпеку на головному шляху та, нарешті,
Винятки в Java - 2
Можливість попередження та вирішення виняткової ситуації в програмі для її продовження – одна з причин використання винятків Java. Механізм винятків також дозволяє захистити написаний вами код (програмний інтерфейс) від неправильного використання користувачем за рахунок валідації (перевірки) вхідних даних. Давайте тепер на секунду побудемо дорожньою службою. По-перше, ви повинні знати місця, де автомобілістів можуть чекати на неприємності. По-друге, вам потрібно заготовити та встановити попереджувальні знаки. І, нарешті, вам потрібно передбачити об'їзні маршрути у разі небезпеки на основному шляху. У Java механізм виключень працює схожим чином. На стадії розробки програми ми «захищаємо» небезпечні ділянки коду щодо виключень за допомогою блоку try{}, передбачаємо «запасні» шляхи за допомогою блоку catch{}, у блоці finally{} ми пишемо код, який виконується у програмі за будь-якого результату. У випадках, коли ми не можемо передбачити «запасний шлях» або навмисно хочемо надати право вибору користувача, ми повинні принаймні попередити його про небезпеку. Чому? А ви тільки уявите обурення водія, який доїде до аварійного мосту, яким не можна проїхати, не зустрівши по дорозі жодного попереджувального знака! У програмуванні при написанні своїх класів та методів ми не завжди можемо передбачати контекст їх використання іншими розробниками у своїх програмах, тому не можемо передбачати на 100% правильний шлях для вирішення виняткової ситуації. У той же час правило хорошого тону — попередити користувачів нашого коду про можливість виняткової ситуації.

Попередження про «неприємності»

Коли ви не плануєте обробляти виняток у своєму методі, але хочете попередити користувачів методу про можливі виняткові ситуації, використовуйте ключове слово throws. Це ключове слово в сигнатурі методу означає, що за певних умов метод може викинути виняток. Таке попередження є частиною інтерфейсу методу та надає право користувачеві на власний варіант реалізації обробника виключення. Після throws ми вказуємо тип виключення, що викидається. Зазвичай, це спадкоємці класу Exception Java. Оскільки Java є об'єктно-орієнтованою мовою, всі винятки в Java є об'єктами.
Винятки в Java - 3

Ієрархія виключень Java

У разі помилки у процесі виконання програми виконуюча середовище JVM створює об'єкт потрібного типу з ієрархії винятків Java – безлічі можливих виняткових ситуацій, успадкованих від загального «предка» – класу Throwable. Виняткові ситуації, що виникають у програмі, можна поділити на дві групи:
  1. Ситуації, за яких відновлення подальшої нормальної роботи програми неможливе
  2. Відновлення можливе.
До першої групи відносять ситуації, коли з'являються винятки, успадковані з класу Error . Це помилки, які виникають під час виконання програми внаслідок збою роботи JVM, переповнення пам'яті чи збою системи. Зазвичай вони свідчать про серйозні проблеми, усунути які програмними засобами неможливо. Такий вид винятків Java відноситься до неконтрольованих (unchecked) на стадії компіляції. До цієї групи також відносять RuntimeException - винятки, спадкоємці класу Exception, що генеруються JVM під час виконання програми. Часто причиною їх є помилки програмування. Ці винятки також є неконтрольованими (unchecked) на стадії компіляції, тому написання коду з їхньої обробки не є обов'язковим. До другої групи відносять виняткові ситуації, що передбачаються ще на стадії написання програми, і для яких має бути написаний код обробки. Такі винятки є контрольованими (checked). Основна частина роботи розробника Java при роботі з винятками – обробка таких ситуацій.

Створення винятку

При виконанні програми виняток генерується JVM або вручну за допомогою оператора throw . При цьому в пам'яті створюється об'єкт виключення та виконання основного коду програми переривається, а обробник винятків JVM намагається знайти спосіб обробити виняток.

Обробка виключення

Створення блоків коду, для яких ми передбачаємо обробку винятків Java, проводиться в програмі за допомогою конструкцій try{}catch, try{}catch{}finally, try{}finally{}.
Винятки в Java - 4
При збудженні виключення в блоці try обробник виключення шукається в наступному блоці catch. Якщо в catch є обробник цього виду виключення - управління переходить до нього. Якщо ні, то JVM шукає обробник цього типу виключення в ланцюжку викликів методів доти, доки не буде знайдено відповідний catch. Після виконання блоку catch керування передається в необов'язковий блок finally . У випадку, якщо відповідний блок catch не знайдений, JVM зупиняє виконання програми і виводить стек викликів методів – stack trace , виконавши перед цим код блоку finally за його наявності. Приклад обробки винятків:
public class Print {

     void print(String s) {
        if (s == null) {
            throw new NullPointerException("Exception: s is null!");
        }
        System.out.println("Inside method print: " + s);
    }

    public static void main(String[] args) {
        Print print = new Print();
        List list= Arrays.asList("first step", null, "second step");

        for (String s:list) {
            try {
                print.print(s);
            }
            catch (NullPointerException e) {
                System.out.println(e.getMessage());
                System.out.println("Exception was processed. Program continues");
            }
            finally {
                System.out.println("Inside bloсk finally");
            }
            System.out.println("Go program....");
            System.out.println("-----------------");
        }

    }
    }
Результати роботи методу main :
Inside method print: first step
Inside bloсk finally
Go program....
-----------------
Exception: s is null!
Exception was processed. Program continues
Inside bloсk finally
Go program....
-----------------
Inside method print: second step
Inside bloсk finally
Go program....
-----------------
Блок finallyзазвичай використовується для того, щоб закрити відкриті в блоці try потоки або звільнити ресурси. Однак при написанні програми не завжди можна встежити за закриттям усіх ресурсів. Для полегшення нашого життя розробники Java запропонували нам конструкцію try-with-resources, яка автоматично закриває ресурси, які відкриті в блоці try. Наш перший приклад можна переписати так за допомогою try-with-resources:
public String input() throws MyException {
    String s = null;
    try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
        s = reader.readLine();
   } catch (IOException e) {
       System.out.println(e.getMessage());
   }
    if (s.equals("")){
        throw new MyException ("String can not be empty!");
    }
    return s;
}
Завдяки можливостям Java, починаючи з версії 7, ми також можемо поєднувати перехоплення різнотипних винятків в одному блоці, роблячи код компактнішим і читабельнішим. Наприклад:
public String input() {
    String s = null;
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
        s = reader.readLine();
        if (s.equals("")) {
            throw new MyException("String can not be empty!");
        }
    } catch (IOException | MyException e) {
        System.out.println(e.getMessage());
    }
    return s;
}

Підсумки

Використання винятків у Java дозволяє підвищити стійкість до відмови програми за рахунок використання «запасних» шляхів, відокремити логіку основного коду від коду обробки виняткових ситуацій за рахунок використання блоків catch, а також дає нам можливість перекласти обробку виключень на користувача нашого коду за допомогою throws.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ