Различни грешки и finally
Проблем
Сметката за нула души
Програмата дели сметката в ресторанта по равно. Вече знаеш за грешния вход и го хващаш:
import java.util.Scanner;
public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("На човек: " + bill / people + " €"); } catch (NumberFormatException e) { System.out.println("Въведи цели числа."); } }}Сметката е 90 €, а някой въвежда 0 за броя на хората. Програмата пак спира:
Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:9)catch е там, но не помага. Хваща само NumberFormatException, а това е друг вид изключение.
Идеята
Няколко catch
След един try може да има няколко catch, по един за всеки вид грешка:
Scanner in = new Scanner(System.in);try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("На човек: " + bill / people + " €");} catch (NumberFormatException e) { System.out.println("Въведи цели числа.");} catch (ArithmeticException e) { System.out.println("Хората не може да са 0.");}| Вход | Изход |
|---|---|
| 90 и 3 | На човек: 30 € |
| 90 и 0 | Хората не може да са 0. |
| 90 и трима | Въведи цели числа. |
Блоковете се проверяват отгоре надолу. Изпълнява се първият, чийто вид отговаря, и само той. Останалите се прескачат.
Scanner in = new Scanner(System.in);try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("На човек: " + bill / people + " €");} catch (NumberFormatException e) { System.out.println("Въведи цели числа.");} catch (ArithmeticException e) { System.out.println("Хората не може да са 0.");}Стъпка по стъпка, с думи
- Първо пускане: сметка 90 €, хората са 0.
- 90 / 0 е деление на нула: хвърля се ArithmeticException.
- Първият catch е за NumberFormatException. Не пасва, прескача се.
- Вторият пасва. Изпълнява се той и само той.
- Второ пускане: вместо брой хора е написано „трима“.
- parseInt не може да направи число: хвърля се NumberFormatException.
- Пасва още първият catch. До втория не се стига.
- Блоковете се проверяват отгоре надолу. Изпълнява се първият, който пасва.
По-точно, за любопитните
- Когато два вида се обработват еднакво, можеш да ги хванеш в един блок с
|:catch (NumberFormatException | ArithmeticException e).
Идеята
Семейството на изключенията
Изключенията са класове, свързани с наследяване. Ето най-честите:
Throwable├── Error сериозен проблем на JVM, не се хваща│ ├── OutOfMemoryError│ └── StackOverflowError└── Exception ├── IOException файлове, мрежа ├── InterruptedException └── RuntimeException ├── ArithmeticException ├── IllegalArgumentException │ └── NumberFormatException ├── IndexOutOfBoundsException │ └── ArrayIndexOutOfBoundsException └── NullPointerExceptioncatch хваща посочения вид и всичките му наследници. Това е полиморфизмът от модула за наследяването: променлива от родителски тип може да сочи обект на наследника. Затова catch (Exception e) хваща всичко:
static void split(String billText, String peopleText) { try { int bill = Integer.parseInt(billText); int people = Integer.parseInt(peopleText); System.out.println("На човек: " + bill / people + " €"); } catch (Exception e) { System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage()); }}
split("90", "3");split("90", "0");split("90", "трима");На човек: 30 €ArithmeticException: / by zeroNumberFormatException: For input string: "трима"e.getClass().getSimpleName() дава името на истинския клас на обекта. Променливата е от тип Exception, но обектът е от наследник.
По-точно, за любопитните
Errorи наследниците му са проблеми на самата JVM, например свършена памет или препълнен стек. Хвърля ги самата JVM. Програмата обикновено не може да направи нищо разумно след тях, затова не се хващат.catch (Throwable t)би хванал и тях. Не го прави.
- Хващай толкова конкретно, колкото можеш. catch (Exception e) скрива и грешки, които не си очаквал, например бъг в собствения ти код.
Пробвай
Кой catch?
Какво ще отпечата програмата?
int[] seats = { 4, 2, 6 };try { System.out.println("Маса 4: " + seats[3] + " места");} catch (ArithmeticException e) { System.out.println("Деление на нула");} catch (RuntimeException e) { System.out.println("Грешка при изпълнение");} catch (Exception e) { System.out.println("Обща грешка");}seats[3] е извън масива с 3 елемента. ArrayIndexOutOfBoundsException не е ArithmeticException, затова първият блок се прескача. Той е наследник на RuntimeException, затова вторият отговаря и се изпълнява. До третия не се стига, макар че и той би отговарял.Пробвай
Намери грешката
Избери реда с грешката.
Exception е родител на ArithmeticException. Първият catch хваща всичко, а вторият никога не може да се стигне. Компилаторът го казва: „exception ArithmeticException has already been caught“. Правилото е от конкретното към общото: размени двата блока.Идеята
Проверени изключения и throws
Някои изключения компилаторът задължава да ги обработиш. Ето метод, който печата бележка и чака принтера с Thread.sleep (пауза в милисекунди):
static void printReceipt() { System.out.println("Печатам бележката..."); Thread.sleep(300); System.out.println("Готово.");}Програмата не се компилира: „unreported exception InterruptedException; must be caught or declared to be thrown“. Паузата може да бъде прекъсната отвън, а компилаторът иска да кажеш какво да стане тогава. Имаш два избора: да хванеш изключението в метода или да го обявиш с throws и да оставиш викащия да реши:
static void printReceipt() throws InterruptedException { System.out.println("Печатам бележката..."); Thread.sleep(300); System.out.println("Готово.");}
try { printReceipt();} catch (InterruptedException e) { System.out.println("Прекъснато.");}Печатам бележката...Готово.- Проверени (checked) са наследниците на
Exception, които не саRuntimeException. Обикновено идват отвън, от неща извън програмата: липсващ файл, прекъсната мрежа, прекъсната пауза. - Непроверени (unchecked) са
RuntimeExceptionи наследниците му. Обикновено са грешка в кода или във входа: индекс, деление на нула, грешен формат. Компилаторът не задължава да ги хващаш.
Най-често ще срещнеш проверено изключение при файловете: IOException.
Идеята
finally: винаги
Принтерът на касата трябва да се освободи след всяка бележка, иначе следващата няма да се отпечата. Каквото и да стане, успех или грешка:
static void printReceipt(String qtyText) { System.out.println("Принтерът е зает."); try { int qty = Integer.parseInt(qtyText); System.out.println("Кафе: " + qty + " x 2 € = " + qty * 2 + " €"); } catch (NumberFormatException e) { System.out.println("Грешно количество: " + qtyText); } finally { System.out.println("Принтерът е свободен."); }}
printReceipt("3");printReceipt("три");Принтерът е зает.Кафе: 3 x 2 € = 6 €Принтерът е свободен.Принтерът е зает.Грешно количество: триПринтерът е свободен.finally се изпълнява винаги: когато всичко мине, когато изключението е хванато и дори когато нито един от тези catch не го хваща. В последния случай finally се изпълнява, а изключението продължава нагоре към следващия try, който може да го хване.
Защо не просто ред след try/catch? Защото той се пропуска, ако изключението е от вид, който catch не хваща, или ако в try има return. finally не се пропуска.
По-точно, за любопитните
- Може и
tryсfinally, безcatch: изключението не се хваща, но почистването се изпълнява.
- За ресурси, които трябва да се затварят, като файлове и връзки с база данни, има по-кратък запис:
try (...)с ресурс в скобите (try-with-resources). Ще го видим, когато стигнем до файловете. Той сам затваря ресурса, както би го затворилfinally.
finallyне се изпълнява само ако програмата бъде спряна изцяло, например сSystem.exit(0).
Пробвай
finally и return
Какво ще отпечата програмата?
static String reserve(int guests) { try { if (guests > 6) { return "Няма маса за " + guests; } return "Маса за " + guests; } finally { System.out.println("Записах опита."); }}
System.out.println(reserve(8));return в try не прескача finally. Методът първо изпълнява finally, после връща текста. Затова „Записах опита.“ се отпечатва преди резултата, който println получава чак когато методът свърши.
Упражнение
Програми с няколко catch
Реши задачите в редактора си. Не печатай въпроси.
1. Цена на човек. Цените са в масив { 120, 75, 300 }. Прочети номер на продукт (от 1) и броя на хората и отпечатай цената на човек с целочислено деление. Хвани три неща: несъществуващ продукт, нула души и всичко останало (тогава отпечатай съобщението на изключението).
| Вход | Изход |
|---|---|
| 2 и 3 | На човек: 25 € |
| 5 и 2 | Няма такъв продукт. |
| 1 и 0 | Броят на хората не може да е 0. |
| 1 и двама | Грешка: For input string: "двама" |
2. Калкулатор. Прочети цяло число, оператор (+, -, * или /) и второ цяло число, всяко на свой ред. Отпечатай сметката. При текст вместо число кажи „Въведи цели числа.“, при деление на нула „Деление на нула.“, а при друг оператор „Непознат оператор: “ и оператора.
84/484 / 4 = 2184/0Деление на нула.Още задачи
- Терминалът. Метод
pay(String amountText)печата „Терминалът е свързан.“, после „Платени X €“ или съобщение за грешен вход, а въвfinally„Терминалът е изключен.“ Извикай го с „25“ и с „двайсет“. - Средна оценка без сривове. Прочети 5 реда с оценки. Пропусни тези, които не са цели числа (изключение) или не са от 2 до 6 (
if). Отпечатай средното с целочислено деление. Ако нито една оценка не е вярна, деленето на нула хвърля: хвани го и отпечатай „Няма верни оценки.“ - Предвиди и провери. Напиши
tryсcatchиfinally, в който всеки блок печата по ред. Предвиди изхода при успех, при хванато и при нехванато изключение и после го пусни.
- Пауза. Напиши метод
pause(int ms), който викаThread.sleep(ms). Направи го веднъж сthrows InterruptedException, веднъж сtry/catchвътре. Какво трябва да направиmainвъв всеки от двата случая?
Тест