МІНІСТЕРСТВО ОСВІТИ ТА НАУКИ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”
Обробка виняткових ситуацій
Методичні вказівки
до виконання лабораторної роботи №5
з курсу “Об’єктно-орієнтоване програмування”
для студентів базового напрямку
6.0804 “Комп’ютерні науки”
ЗАТВЕРДЖЕНО
на засіданні кафедри “Системи автоматизованого проектування” Протокол № 1від 30.08.2010
ЛЬВІВ 2010 Мова програмування Java. Методичні вказівки до виконання лабораторної роботи №5 “Обробка виняткових ситуацій” з курсу: “Об’єктно-орієнтоване програмування” для студентів базового напрямку 6.0804 “Комп’ютерні науки”.
Укладачі: Каркульовський В.І., доцент, к.т.н.
Керницький А.Б., ст.викл., др.інж.
Відповідальний за випуск:
Рецензенти:
1. МЕТА РОБОТИ
Одержати навики роботи із оброблення виняткових ситуацій.
2.ОСНОВНІ ТЕОРЕТИЧНІ ВІДОМОСТІ
2.1. ВИНЯТКОВІ СИТУАЦІЇ У JAVA
Виняткові ситуації (exceptions) можуть виникнути під час виконання (runtime) програми, перервавши її звичайний хід. До них відносится ділення на нуль, відсутність завантажуваного файла, індекс масива який став від’ємним або вийшов за верхню межу, переповнення виділеної пам’яті і багато інших неприємностей. Звичайно, можна передбачити такі ситуації і застрахуватися від них, наприклад таким чином:
if (something == wrong){
// Робимо аварійні дії
}else{
// Звичайний хід дій
}
Але при цьому багато часу іде на перевірки, і програма перетворюється у набір таких перевірок. Якщо переглянути промислову програму, написану мовою С або Pascal, то можна побачити, що вона на 2/3 складається із таких перевірок. В об’єктно-орієнтованих мовах програмування прийнято інший підхід. При виникненні виняткової ситуації виконуюча система створює об’єкт певного класу, який відповідає ситуації, що виникла і який містить дані про те, що, де і коли трапилося. Цей об’єкт передається на обробку програмі, в якій виникла виняткова ситуація. Якщо програма не обробляє виняткову ситуацію, тоді об’єкт за замовчуванням повідомляє обробнику виконуючої системи. Обробник поступає дуже просто: виводить на консоль повідомлення про виянткову ситуацію і зупиняє виконання програми.
Приклад. У програмі лістингe 2.1 може виникнути ділення на нуль, якщо запустити її з аргументом 0. E програмі немає жодних засобів обробки такої виняткової ситуації. Подивіться на рис. 2.1, які повідомлення виводить виконуюча система Java.
Лістинг 2.1. Програма без обробки виняткових ситуацій
class SimpleExt {
public static void main(String[] args){
int n = Integer.parseInt(args[0]);
System.out.println("10 / n = " + (10 / n));
System.out.println("After all actions");
}
}
Програма SimpleExt запущена три рази. Перший раз аргумент args[0] дорівнює 5 і програма виводить результат: "10 / n = 2". Після цього з’являється друге повідомлення: "After all actions". Другий раз аргумент дорівнює 0, і замість результата ми отримуємо повідомлення про те, що в підпроцесі "main" відбулося виняткова ситуація у класі ArithmeticException внаслідок ділення на нуль: "/ by zero". Далі уточнюється, що виняткова ситуація виникло під час виконання метода main класу SimpleExt, а в дужках вказано, що дія, в результаті якої виникла виняткова ситуація, записана у четвертому рядку файла SimpleExt.java. Виконання програми зупиняється, заключне повідомлення не з’являється. Третій раз программа запущена взагалі без аргумента. В масиві args немає елементів, його довжина дорівнює нулю, і ми намагаємося звернутися до елемента args[0]. Виникає виняткова ситуація в класі ArrayIndexOutOfBoundsException внаслідок дії, записаної у третьому рядку файла SimpleExt.java. Виконання програми зупиняється, звернення до методу println() не відбувається.
Рис. 2.1. Повідомлення про виняткові ситуації
2.1.1. Блоки перехоплення виняткової ситуації
Ми можемо перехопити і обробити виняткову ситуацію у програмі. При описуванні обробки застосовується бейсбольна термінологія. Говорять, що виконуюча система або програма "викидає" (throws) об’єкт-виключення. Цей об’єкт "пролітає" через всю програму, з’явившись спочатку у тому методі, де відбулася виняткова ситуація, а програма в одному або декількох місцях намагається (try) її "перехопити" (catch) і обробити. Оброблення можна зробити повністю в одному місці, а можна обробити виняткову ситуацію в одному місці, викинути знову, перехопити у другому місці і обробляти далі.
Добре написані об’єктно-орієнтовані програми обовязково повинні обробляти всі виникаючі в них виняткові ситуації. Для того щоб спробувати (try) перехопити (catch) об’єкт-виняток, потрібно весь код програми, у якому може виникнути виняткова ситуація, охопити оператором try {} catch() {}. Кожний блок catch(){} перехоплює виняткову ситуацію тільки одного типу, вказаного в його аргументі. Але можна написати декілька блоків catch(){} для перехоплення декількох типів виняткових ситуацій. Наприклад, у програмі лістинга 2.1 можуть виникнути виняткові ситуації двох типів. Напишемо блоки їх обробки, як це зроблено в лістинзі 2.2.
Лістинг 2.2. Програма з блоками обробки виключень
class SimpleExt1{
public static void main(String[] args){
try{
int n = Integer.parseInt(args[0]);
System.out.println("After parseInt()");
System.out.println(" 10 / n = " + (10 / n) ) ;
System. out. println ("After results output");
}catch(ArithmeticException ae){
System.out.println("From Arithm.Exc. catch: "+ae);
}catch(ArrayIndexOutOfBoundsException arre){
System.out.println("From Array.Exc.catch: "+arre);
}finally{
System.out.println("From finally");
}
System.out.println("After all actions");
}
}
У програму лістинга 2.2 вставлено блок try{} і два блоки перехоплення catch(){} для кожного типу виняткових ситуацій. Тут обробка виняткових ситуацій полягає просто у виведенні повідомлення і вмісту об’єкта-виключення, як воно представлено методом toString() відповідного класу-виключення. Після блоків перехоплення вставлено ще один, необов’язковий блок finally(). Він призначений для виконання дій, які треба виконати обов’язково, щоб не трапилося. Все, що написано у цьому блоці, буде виконано і при виникненні виняткової ситуації, і при звичайному ході програми, і навіть якщо вихід із блоку try{} здійснюється оператором return.
Якщо в операторі обробки виняткових ситуацій є блок finally{}, тоді блок catch() {} може бути відсутнім, тобто можна не перехоплювати виняткову ситуацію, але при її появі виконає певні обов’язкові дії. Крім блоків перехоплення у лістингу 2.2 після кожної дії робиться трасуючий друк, щоб можна було прослідкувати за порядком виконання програми. Програма запущена три рази: з аргументом 5, з аргументом 0 і без аргумента. Результат показано на рис. 2.2.
Рис. 2.2. Повідомлення обробки виняткових ситуацій
Після першого запуску, при звичайному ході програми, виводяться всі дії. Після другого запуску, що веде до ділення на нуль, управління одразу ж передається у відповідний блок catch(ArithmeticException ае) {}, потім виконується те, що написано у блоці finally{}. Після третього запуску на виконання методу parseInt() управління передається у другий блок catch(ArraylndexOutOfBoundsException arre){}, а потім у блок finally{}.
У всіх випадках — і при звичайному виконанні програми, і після даних обробок — виводиться повідомлення "After all actions". Це свідчить про те, що виконання програми не зупиняється при виникненні виняткової ситуації, як це було у програмі лістингу 2.1, а продовжується після обробки і виконання блоку finally {}. Порожній блок catch (){}, у якому між фігурними дужками нічого немає, теж вважається обробкою виняткової ситуації і приводить до того, що виконання програми не зупиниться. Саме так ми "обробляли" виняткові ситуації на попередніх лабораторних роботах.
Змінимо програму лістингу 16.2, винісши ділення в окремий метод f(). Одержимо лістинг 2.3.
Лістинг 2.3. Видалення виключень із методу
class SimpleExt2{
private static void f(int n){
System.out.println(" 10 / n = " + (10 / n));
}
public static void main(String[] args){
try{
int n = Integer.parseInt(args[0]);
System.out.println("After parseInt()");
f (n);
System.out.println("After results output");
}catch(ArithmeticException ae){
System.out.println("From Arithm.Exc. catch: "+ae);
}catch(ArrayIndexOutOfBoundsException arre){
System.out.println("From Array.Exc. catch: "+arre);
}finally{
System.out.println("From finally");
}
System.out.println("After all actions");
}
}
Відкомпілювавши і запустивши програму лістингу 2.3, впевнимося, що виведення програми не змінилося, воно таке саме, як на рис. 2.2. Виняткова ситуація, що виникла при діленні на нуль у методі f(), "пролетіло" через цей метод, "вилетіло" в метод main(), там перехоплено і оброблено.
2.1.2. Частина заголовка методу throws
Та обставина, що метод не обробляє виникаючих виняткових ситуацій, а викидає (throws), потрібно відмічати у заголовку методу службовим словом throws і вказанням класу виключення:
private static void f(int n) throws ArithmeticException{
System.out.println(" 10 / n = " + (10 / n)) ;
}
Чому цього не було зроблено у лістингу 2.3? Справа у тому, що специфікація JLS ділить всі виняткові ситуації на перевіряючі (checked), тобто, ті, які перевіряє компілятор, і неперевіряючі (unchecked). При перевірці компілятор помічає необроблені в методах і конструкторах виняткові ситуації і вважає за помилку відсутність у заголовку таких методів і конструкторів з поміткою throws. Саме для уникнення подібних помилок на попередніх лабораторних ми вставляли у лістинги блоки обробки винятквих сиуацій. Виняткові ситуації класу RuntimeException і його підкласів, одним із яких є ArithmeticException, є неперевіряючими, для них помітка throws необов’язкова. Ще одним великим сімейством неперевіряючих виняткових ситуацій є клас Error і його розширення. Причина в тому, що компілятор не перевіряє ці типи виняткових ситуацій, понлягає в тому, що виняткові ситуації класу RuntimeException свідчать про помилки в програмі, і єдиним методом їхньої обробки — виправити вихідний текст програми і перекомпілювати її. Що стосується класу Error, то ці виняткові ситуації дуже важко локалізувати (неможливо визначити місце їх появи) на стадії компіляції.
Виникнення перевіряючої виняткової ситуації показує, що програма недостатньо продумана, не всі можливі ситуації описані. Така програма повинна бути допрацьована, про що нагадує компілятор. Якщо метод або конструктор видає декілька виняткових ситуацій, тоді їх потрібно переррахувати через кому після слова throws. Якби виняткові ситуації не були б об’єктами підкласів класу RuntimeException, тоді заголовок метода main() у лістингу 2.1 потрібно було б написати нступним чином:
public static void main(String[] args)
throws ArithmeticException, ArrayIndexOutOfBoundsException{
// Зміст методу
}
Перенесемо тепер обробку ділення на нуль у метод f() і додамо трасуючий друк, як це зроблено в лістингу 2.4. Результат — на рис. 2.3.
Лістинг 2.4. Обобка виняткової ситуації у методі
class SimpleExt3{
private static void f(int n){ // throws ArithmeticException{
try{
System.out.println(" 10 / n = " + (10 / n) ) ;
System.out.println("From f() after results output");
}catch(ArithmeticException ae){
System.out.println("From f() catch: " + ae) ;
// throw ae;
}finally{ System.out.println("From f() finally"); }
}
public static void main(String[] args){
try{
int n = Integer.parseInt(args[0]);
System.out.println("After parseInt()");
f (n);
System.out.println("After results output"); }
catch(ArithmeticException ae){
System.out.println("From Arithm.Exc. catch: "+ae);
}catch(ArrayIndexOutOfBoundsException arre){
System.out.println("From Array.Exc. catch: "+arre);
}finally{ System.out.println("From finally");}
System.out.println("After all actions");
}
}
Рис. 2.3. Обробка виняткової ситуації у методі
Уважно прослідкуйте за передачею управління і зауважте, що виняткові ситуації класу ArithmeticException уже не викидаються в метод main(). Оператор try {} catch() {} у методі f() можна розглядати як вкладений оператор обробки виняткових ситуацій у методі main(). При необхідності виняткову ситуацію можна викинути оператором throw(). У лістингу 2.4 цей оператор показаний як коментар. Заберіть символ коментаря //, перекомпілюйте програму і подивіться, як зміниться її виведення.
2.1.3. Оператор throw
Цей оператор дуже простий: післе слова throw через пробіл записується об’єкт класу-виняткової ситуації. Досить часто він створюється прямо в операторі throw, наприклад:
throw new ArithmeticException();
Оператор можна записати у будь-якому місці програми. Він негайо викидає записаний в ньому об’єкт-виняткову ситуацію і далі обробка цієї виняткової ситуації іде як звичайно, так ніби у цьому місці відбулося ділення на нуль або інша дія, що викликала виняткову ситуацію класу ArithmeticException. Отже, кожний блок catch() перехоплює один певний тип виняткових ситуацій. Якщо потрібно обробити декілька типів винятквих ситуацій, то можна скористатися тим, що класи-виняткові ситуації утворюють ієрархію. Змінимо ще раз лістинг 2.2, одержавши лістинг 2.5.
Лістинг 2.5. Обробка декількох типів виняткових ситуацій
class SimpleExt4{
public static void main(String[] args){
try{
int n = Integer.parseInt(args[0]);
System.out.println("After parseInt()");
System.out.println(" 10 / n = " + (10 / n) ) ;
System.out.println("After results output");
}catch(RuntimeException ae){
System.out.println("From Run.Exc. catch: "+ae);
}finally{
System.out.println("From finally");
}
System.out.println("After all actions");
}
}
У лістингу 2.5 два блоки catch() {} замінені одним блоком, який перехоплює виняткову ситуацію класу RuntimeException. Як показано на рис. 2.4, цей блок перехоплює обидві виняткові ситуації - тому що це виняткові ситуації підкласів класу RuntimeException.
Рис. 2.4. Перехоплення декількох типів виняткових ситуацій
Таким чином, переміщаючись по ієрархії класів-виняткових ситуацій, ми можемо обробляти одразу крупні групи виняткових ситуацій. Розглянемо детальніше ієрархію класів-виняткових ситуацій.
2.1.4. Ієрархія класів-виняткових ситуацій
Всі класи-виняткових ситуацій розширюють клас ThrowabІe — безпосереднє розширення класу Оbject. У класі ThrowabІe і у всіх його розширеннях є два конструктори:
ThrowabІe() — конструктор за замовчуванням;
ThrowabІe (String message) — створюваний об’єкт буде містити довільне повідомлення message.
Повідомлення, записане у конструкторі, можна отримати згодом за допомогою методу getMessage(). Якщо об’єкт створювася конструктором за замовчуванням, тоді даний метод поверне null. Метод toString() повертає короткий опис подї (саме він працював у попередніх лістингах). Три методи виводять повідомлення про всі методи, що зустрілися на шляху "польоту" виняткової ситуації:
printstackTrace() — виводить повідомлення у стандартне виведення, як правило, це консоль;
printStackTrace(PrintStream stream) — виводить повідомлення у байтовий потік stream;
printStackTrace(PrintWriter stream) — виводить повідомлення у символьний потік stream.
У класі ThrowabІe є два безпосередні нащадки— класи Error і Exception. Вони не додають нових методів, а служать для розподілу класів-виняткових ситуацій на два великиих сімейства - сімейство класів-помилок (error) і сімейство власне класів-виняткових ситуацій (exception). Класи-помилки, які розширяють клас Error, свідчать про виникнення складних ситуацій у віртуальній машині Java. Їх оброблення вимагає глибокого розуміння всіх тонкощів роботи JVM. Їх не рекомендується виконувати у звичайній програмі. He потрібно створювати свої класи-виняткові ситуації розширеннями класу Error або будь-якого його підкласу. Імена класів-помилок закінчуються словом Error. Класи-виняткові ситуації, які розширяють клас Exception, вказують на виникнення звичайної позаштатної ситуації, яку можна і потрібно обробити. Такі виняткові ситуації належить викинути оператором throw. Класів-виняткових ситуацій є дуже багато - більше двохсот. Вони розміщені майже у всіх пакетах J2SDK. У більшості випадків можна підібрати готовий клас-виняткову ситуацію для обробки виняткових ситуацій у своїй програмі. При бажанні можна створити і свій клас, розширивши клас Exception або будь-який його підклас.
Серед класів-виняткових ситуацій виділяється клас RuntimeException — пряме розширення класу Exception. У ньому і його підкласах відмічаються виянткові ситуації, які виникли при роботі JVM, але не такі серйозні, як помилки. Їх можна обробляти і викидати, розширяти своїми класами, але краще довірити це JVM, оскільки найчастіше це просто помилка у програмі, яку потрібно виправити. Особливість виняткових ситуацій даного класу є у тому, що їх не потрібно відмічати у заголовку метода поміткою throws. Імена класів-виняткових ситуацій закінчуються словом Exception.
2.1.5. Порядок оброблення виняткових ситуацій
Блоки catch () {} перехоплюють виняткові ситуації в порядку написання цих блоків. Це правило приводить до цікавих результатів. У лістингу 2.2 було записано два блоки перехоплення catch() і обидва блоки виконувались при виникненні відповідної виняткові ситуації. Це відбувалося тому, що класи-виняткові ситуації ArithmeticException і ArrayindexOutofBoundsException знаходяться на різних гілках ієрархії виняткових ситуацій. Інший результат буде, якщо блоки catch() {} перехоплюють виняткові ситуації, розташовані на одній гілці. Якщо у лістингу 2.4 після блоку, який перехоплює RuntimeException, помістити блок, який обробляє вихід індекса за межі:
try{
// Оператори, які викликають виняткові ситуації
}catch(RuntimeException re){
// Певна обробка
}catch(ArrayIndexOutOfBoundsException ae){
// Ніколи не буде виконано!
}
то він не буде виконуватися, оскільки виняткова ситуація цього типу є, до того ж, винятковою ситуацією загального типу RuntimeException і буде перехоплюватися попереднім блоком catch () {}.
2.1.6. Створення власних виянткових ситуацій
Перш за все, потрібно чітко визначити ситуації, у яких будуть виникати власні виняткові ситуації, і подумати, не чи не стане їхнє перехоплення перехоплювати також і інші, не враховані програмістом виняткові ситуації. Потім потрібно вибрати суперклас для створюваного класу-виняткової ситуації. Ним може бути клас Exception або один із його чисельних підкласів. Після цього можна написати клас- виняткову ситуацію. Його ім’я повинно завершуватися словом Exception. Як правило, цей клас складається лише з двох конструкторів і перевизначення методів toString() і getMessage().
Розглянемо приклад. Нехай метод handle(int cipher) обробляє цифри 0—9, які передаються йому в аргументі cipher типу int. Ми хочемо викинути виняткову ситуацію, якщо аргумент cipher виходить за діапазон 0—9. Перш за все, впевнимося, що такого виключення немає в ієрархіїи класів Exception. До всього іншого, не відслідковується і більш загальна ситуація попадання цілого числа в певний діапазон. Тому будемо розширяти клас. Назвемо його cipherException, прямо від класу Exception. Визначимо клас CipherException, як показано в лістингу 2.6, і використаємо його у класі ExceptDemo. На рис. 2.5 продемонстровано виведення цієї програми.
Лістинг 16.6. Створення класу-виняткової ситуації
class CipherException extends Exception{
private String msg;
CipherException(){ msg = null;}
CipherException(String s){ msg = s;}
public String toString(){
return "CipherException (" + msg + ")" ;
}
}
class ExceptDemo{
static public void handle(int cipher) throws CipherException{
System.out.println("handle()'s beginning");
if (cipher < 0 || cipher > 9)
throw new CipherException("" + cipher);
System.out.println("handle()'s ending");
}
public static void main(String[] args){
try{
handle(1) ;
handle(10); }
catch(CipherException ce){
System.out.println("caught " + ce) ;
ce.printStackTrace(); }
}
}
Рис. 2.5. Обробка власної виняткової ситуації
2.1.7. Висновки
Обробка виянткових ситуацій стала обов’язковою частиною об’єктно-орієнтованих програм. Застосовуючи методи класів J2SDK та інших пакетів, потрібно звертати увагу на те, які виняткові ситуації вони викидають, і обробляти їх. Виняткові ситуації різко змінюють хід виконання програми, роблять його заплутаним. Не потрібно займатися складною обробкою. Наприклад, із блоку finally{} можна викинути виняткову ситуацію і обробити його в іншому місці. Подумайте, що відбудеться в цьому випадку із винятковою ситуацією, що виникла у блоці try{}? Вона ніде не буде перехоплена і оброблена.
3.КОНТРОЛЬНІ ЗАПИТАННЯ
Що називається винятковою ситуацією?
4.ЛАБОРАТОРНЕ ЗАВДАННЯ
5. ЗМІСТ ЗВІТУ
Мета роботи.
Короткі теоретичні відомості.
Файли проекту.
Аналіз результатів та висновки.
СПИСОК РЕКОМЕНДОВАНОЇ ЛІТЕРАТУРИ
Bruce Eckel, Thinking in Java, 2nd Edition, 2000.
Christopher Batty and David Scuse, Installing Eclipse. Department of Computer Science, University of Manitoba, Winnipeg, Manitoba, Canada, 2003.