Когато нещо се обърка
Проблем
12а вместо 12
Касата за концерта пита колко билета искаш. Билетът е 15 €.
import java.util.Scanner;
public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Колко билета? "); int tickets = Integer.parseInt(in.nextLine()); System.out.println("Общо: " + tickets * 15 + " €"); }}С вход 12 програмата казва „Общо: 180 €“. Но клиентът бърза и натиска една буква повече:
Колко билета? 12аException in thread "main" java.lang.NumberFormatException: For input string: "12а" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at java.base/java.lang.Integer.parseInt(Integer.java:778) at Main.main(Main.java:7)Програмата спира. Вместо сумата излиза дълъг текст, а редът „Общо“ изобщо не се стига. Какво значи този текст и как да не спира програмата заради една буква?
Идеята
Изключение
Integer.parseInt трябва да върне число. От „12а“ не може да направи число, а да върне каквото и да е число би било лъжа. Затова методът създава обект, който описва проблема, и го хвърля. Този обект се казва изключение (exception).
Ако никой не го хване, програмата спира и показва какво е станало. Този текст се казва stack trace и се чете отгоре надолу:
- Първият ред: видът на изключението и съобщението.
NumberFormatException: For input string: "12а": грешен формат на числото, ето кой текст. - Редовете с „at“: къде е станало. Това е стекът от извиквания от урока за рекурсията, отвътре навън. Най-горе е методът, който е хвърлил изключението, а най-долу е
main. - Твоят ред: първият ред „at“, който сочи твоя файл. Тук:
Main.java:7. Редовете над него са в кода на Java и рядко ти трябват.
Съобщенията са на английски, защото ги пише езикът, не ти.
Пробвай
Намери грешката
Програмата трябва да отпечата последния резултат от контролното. Тя спира с това:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5 at Main.lastScore(Main.java:3) at Main.main(Main.java:8)Прочети stack trace-а. На кой ред от програмата е грешката?
Избери реда с грешката.
lastScore, ред 3. Редът под него е main, ред 8: оттам е извикан методът, но грешката не е там. Първият ред казва и каква е тя: индекс 5 при дължина 5. Последният индекс е scores.length - 1.Идеята
try и catch
Изключението може да се хване. Кодът, който може да хвърли, отива в try („опитай“). Какво да стане при грешка, пишеш в catch („хвани“):
Scanner in = new Scanner(System.in);System.out.print("Колко билета? ");try { int tickets = Integer.parseInt(in.nextLine()); System.out.println("Общо: " + tickets * 15 + " €");} catch (NumberFormatException e) { System.out.println("Това не е цяло число.");}System.out.println("Благодарим ти!");12аКолко билета? Това не е цяло число.Благодарим ти!- В скобите на
catchпишеш кой вид изключение хващаш. - Ако в
tryвсичко мине,catchсе прескача. С вход12изходът е „Общо: 180 €“ и „Благодарим ти!“. - След
catchпрограмата продължава нататък. Тя вече не спира.
Scanner in = new Scanner(System.in);System.out.print("Колко билета? ");try { int tickets = Integer.parseInt(in.nextLine()); System.out.println("Общо: " + tickets * 15 + " €");} catch (NumberFormatException e) { System.out.println("Това не е цяло число.");}System.out.println("Благодарим ти!");Стъпка по стъпка, с думи
- Програмата пита колко билета. Въвеждаш „12а“.
- В try: parseInt опитва да направи число от „12а“.
- Не може. Методът хвърля NumberFormatException.
- Изпълнението скача направо в catch. Редът с „Общо“ се прескача.
- catch е за точно този вид грешка: печата съобщението за клиента.
- След catch програмата продължава. Не спира.
По-точно, за любопитните
- Променлива, обявена в
try(тукtickets), съществува само до затварящата скоба наtry. Ако ти трябва и след блока, обяви я предиtry.
- В Java името е задължително:
catch (NumberFormatException e), дори да не го ползваш. По навик то еe.
Пробвай
Кое се изпълнява?
Какво ще отпечата програмата?
System.out.println("Начало");try { System.out.println("Чета"); int seats = Integer.parseInt("десет"); System.out.println("Прочетох " + seats);} catch (NumberFormatException e) { System.out.println("Грешен вход");}System.out.println("Край");„Чета“ се отпечатва, защото е преди грешката. После parseInt хвърля и изпълнението скача направо в catch. „Прочетох“ се пропуска. След catch програмата продължава с „Край“.
Идеята
Какво пише в изключението
Изключението е обект, а e в catch (NumberFormatException e) е променливата, която сочи към него. Обектът има метод за съобщението: e.getMessage().
Scanner in = new Scanner(System.in);try { int tickets = Integer.parseInt(in.nextLine()); System.out.println("Общо: " + tickets * 15 + " €");} catch (NumberFormatException e) { System.out.println("Грешка: " + e.getMessage());}12аГрешка: For input string: "12а"Съобщението е полезно за теб, когато търсиш грешката. На клиента обикновено показваш свой текст на неговия език, като „Това не е цяло число.“
Идеята
Питай, докато е вярно
Клиентът сбърка. Не е нужно да спираш. Питай отново:
Scanner in = new Scanner(System.in);int tickets;while (true) { System.out.print("Колко билета? "); try { tickets = Integer.parseInt(in.nextLine()); break; } catch (NumberFormatException e) { System.out.println("Въведи цяло число, например 3."); }}System.out.println("Общо: " + tickets * 15 + " €");12адванайсет12Колко билета? Въведи цяло число, например 3.Колко билета? Въведи цяло число, например 3.Колко билета? Общо: 180 €while (true) се върти, докато break не го прекъсне. А break се стига само ако parseInt е успял. При грешка изпълнението скача в catch, след него цикълът започва отначало и пита пак.
По-точно, за любопитните
- В Java няма
TryParse. Хващането наNumberFormatExceptionе обичайният начин.Scannerима иhasNextInt(), която проверява дали следва цяло число, без да го чете.
Пробвай
Деление на нула
Не всяко изключение идва от входа. Средната стойност на поръчка е приходът, делен на броя поръчки. Но в първия ден поръчки няма. С цели числа revenue / orders спира програмата:
Exception in thread "main" java.lang.ArithmeticException: / by zeroА с дробни числа?
Какво ще отпечата програмата?
int revenue = 90;int orders = 0;double average = (double) revenue / orders;System.out.println(average);NaN („не е число“). Програмата продължава с грешна стойност, без да каже нищо. Затова преди такова деление провери делителя с if.По-точно, за любопитните
- Целите числа нямат стойност за безкрайност, затова там остава само изключението. При дробните числа правилата идват от стандарта за дробни числа IEEE 754, който всички езици следват.
Упражнение
Програми, които не спират
Реши задачите в редактора си. Не печатай въпроси, за да съвпадне изходът ти с примерния.
1. Текст към число. Напиши метод toNumber(String text), който връща текста като double. Прочети два реда. За всеки отпечатай числото или съобщение, че не е число. Хвани изключението в main.
123.45123.abcЧислото е: 123.45Грешка: 123.abc не е число.2. Няма такъв продукт. Цените са в масив { 120, 75, 300, 45, 89 }. Прочети номер на продукт от 1 до 5 и отпечатай цената му. Ако номерът е извън масива, хвани изключението и го кажи. С вход 2 изходът е Цена: 75 €.
7Няма продукт с номер 7.3. Средна поръчка. Прочети прихода и броя поръчки (цели числа) и отпечатай средното с целочислено деление. Ако поръчки няма, хвани изключението. С вход 900 и 4 изходът е Средно: 225 €.
900Още няма поръчки.Още задачи
- Възраст, докато е вярна. Питай за възраст, докато входът не е цяло число от 0 до 120. За текст като „двайсет“ кажи „Това не е число.“, а за 150 кажи „Възрастта е от 0 до 120.“ Едното е изключение, другото е
if. - Сума на верните. Прочети 5 реда. Събери тези, които са цели числа, и преброй пропуснатите: при вход
10,abc,5,7.5,3изходът еСума: 18, пропуснати: 2. - Деление по желание. Прочети две цели числа и отпечатай частното. При текст вместо число и при деление на нула програмата не спира, а пита отново за двете числа.
Тест