1. Видове изключения
Всички изключения са разделени на 4 типа, които всъщност са класове, които се наследяват един друг.
Throwable
клас
Базовият клас за всички изключения е класът Throwable
. Класът Throwable
съдържа codeа, който записва текущия стек за извикване (проследяване на стека на текущия метод) в масив. Ще научим Howво е проследяване на стека малко по-късно.
Операторът за хвърляне може да приеме само обект, който произлиза от Throwable
класа. И въпреки че теоретично можете да напишете code като throw new Throwable();
, обикновено никой не прави това. Основната цел на Throwable
класа е да има един родителски клас за всички изключения.
Error
клас
Следващият клас изключение е Error
класът, който директно наследява Throwable
класа. Java машината създава обекти от Error
класа (и неговите наследници), когато възникнат сериозни проблеми . Например хардуерна неизправност, недостатъчна памет и др.
Обикновено, като програмист, не можете да направите нищо в ситуация, в която такава грешка (видът, за който Error
трябва да бъде хвърлен) е възникнала в програмата: тези грешки са твърде сериозни. Всичко, което можете да направите, е да уведомите потребителя, че програмата се срива и/or да запишете цялата известна информация за грешката в регистрационния файл на програмата.
Exception
клас
Класовете Exception
и RuntimeException
са за често срещани грешки, които се случват при работата на много методи. Целта на всяко хвърлено изключение е да бъде уловено от catch
блок, който знае How правилно да се справи с него.
Когато даден метод не може да завърши работата си по няHowва причина, той трябва незабавно да уведоми извикващия метод, като хвърли изключение от съответния тип.
С други думи, ако една променлива е равна на null
, методът ще хвърли NullPointerException
. Ако неправилните аргументи са бor предадени на метода, той ще изведе InvalidArgumentException
. Ако методът случайно раздели на нула, той ще хвърли ArithmeticException
.
RuntimeException
клас
RuntimeExceptions
са подмножество на Exceptions
. Можем дори да кажем, че това RuntimeException
е олекотена version на обикновените изключения ( Exception
) — на такива изключения се налагат по-малко изисквания и ограничения
Ще научите разликата между Exception
и RuntimeException
по-късно.
2. Throws
: проверени изключения
Всички изключения на Java попадат в 2 категории: проверени и непроверени .
Всички изключения, които наследяват RuntimeException
or Error
се считат за непроверени изключения . Всички останали са проверени изключения .
Двадесет години след като бяха въведени проверените изключения, почти всеки Java програмист смята това за грешка. В популярните съвременни рамки 95% от всички изключения не са отметнати. Езикът C#, който почти точно копира Java, не добави проверени изключения .
Каква е основната разлика между проверените и непроверените изключения?
Има допълнителни изисквания, наложени на проверените изключения. Грубо казано, те са следните:
Изискване 1
Ако даден метод хвърля проверено изключение , той трябва да посочи типа изключение в своя подпис . По този начин всеки метод, който го извиква, е наясно, че това "смислено изключение" може да възникне в него.
Посочете проверените изключения след параметрите на метода след throws
ключовата дума (не използвайте throw
ключовата дума по погрешка). Изглежда нещо подобно:
type method (parameters) throws exception
Пример:
проверено изключение | непроверено изключение |
---|---|
|
|
В примера вдясно, нашият code хвърля непроверено изключение — не са необходими допълнителни действия. В примера отляво методът хвърля проверено изключение, така че throws
ключовата дума се добавя към подписа на метода заедно с типа на изключението.
Ако даден метод очаква да хвърли множество проверени изключения, всички те трябва да бъдат посочени след throws
ключовата дума, разделени със запетаи. Редът не е важен. Пример:
public void calculate(int n) throws Exception, IOException
{
if (n == 0)
throw new Exception("n is null!");
if (n == 1)
throw new IOException("n is 1");
}
Изискване 2
Ако извикате метод, който е проверил изключения в сигнатурата си, не можете да пренебрегнете факта, че той ги хвърля.
Трябва or да уловите всички такива изключения, като добавите catch
блокове за всяко от тях, or като ги добавите към throws
клауза за вашия метод.
Сякаш казваме: „ Тези изключения са толкова важни, че трябва да ги уловим. И ако не знаем How да се справим с тях, тогава всеки, който може да извика нашия метод, трябва да бъде уведомен, че такива изключения могат да възникнат в него.
Пример:
Представете си, че пишем метод за създаване на свят, населен с хора. Първоначалният брой хора се предава като аргумент. Така че трябва да добавим изключения, ако има твърде малко хора.
Създаване на Земята | Забележка |
---|---|
|
Методът потенциално хвърля две проверени изключения:
|
Това извикване на метод може да се обработва по 3 начина:
1. Не хващайте ниHowви изключения
Това най-често се прави, когато методът не знае How правилно да се справи със ситуацията.
Код | Забележка |
---|---|
|
Извикващият метод не улавя изключенията и трябва да информира другите за тях: той ги добавя към собствената си throws клауза |
2. Хванете някои от изключенията
Ние се справяме с грешките, с които можем да се справим. Но тези, които не разбираме, ги хвърляме към извикващия метод. За да направим това, трябва да добавим името им към клаузата throws:
Код | Забележка |
---|---|
|
Обаждащият се улавя само едно проверено изключение — LonelyWorldException . Другото изключение трябва да се добави към сигнатурата му, като се посочи след throws ключовата дума |
3. Уловете всички изключения
Ако методът не хвърля изключения към извикващия метод, тогава извикващият метод винаги е уверен, че всичко е работило добре. И няма да може да предприеме ниHowви действия за коригиране на извънредни ситуации.
Код | Забележка |
---|---|
|
Всички изключения се улавят в този метод. Обаждащият се ще бъде уверен, че всичко е минало добре. |
3. Опаковане на изключения
Проверените изключения изглеждаха готини на теория, но се оказаха огромно разочарование на практика.
Да предположим, че имате супер популярен метод във вашия проект. Извиква се от стотици места във вашата програма. И вие решавате да добавите ново отметнато изключение към него. И може би това проверено изключение е наистина важно и толкова специално, че само main()
методът знае Howво да прави, ако бъде хванат.
Това означава, че ще трябва да добавите провереното изключение към throws
клаузата на всеки метод, който извиква вашия супер популярен метод . Както и в throws
клаузата на всички методи, които извикват тези методи. И на методите, които извикват тези методи.
В резултат на това throws
клаузите на половината от методите в проекта получават ново проверено изключение. И, разбира се, вашият проект е покрит от тестове и сега тестовете не се компorрат. И сега трябва да редактирате и клаузите за хвърляния във вашите тестове.
И тогава целият ви code (всички промени в стотици файлове) ще трябва да бъде прегледан от други програмисти. И в този момент се питаме защо направихме толкова много кървави промени в проекта? Ден(и?) работа и повредени тестове – всичко това в името на добавянето на едно проверено изключение?
И разбира се, все още има проблеми, свързани с наследяването и преодоляването на метода. Проблемите, които идват от проверените изключения, са много по-големи от ползата. Изводът е, че сега малко хора ги обичат и малко хора ги използват.
Въпреки това все още има много code (включително standardн code на Java библиотека), който съдържа тези проверени изключения. Какво да се прави с тях? Не можем да ги пренебрегнем и не знаем How да се справим с тях.
Програмистите на Java предложиха да обгърнат проверените изключения в RuntimeException
. С други думи, уловете всички проверени изключения и след това създайте непроверени изключения (например RuntimeException
) и ги изхвърлете instead of това. Пequalsето на това изглежда нещо подобно:
try
{
// Code where a checked exception might occur
}
catch(Exception exp)
{
throw new RuntimeException(exp);
}
Това не е много красиво решение, но тук няма нищо престъпно: изключението беше просто напъхано в RuntimeException
.
Ако желаете, можете лесно да го извлечете от там. Пример:
Код | Забележка |
---|---|
|
Вземете изключението, съхранено вътре в RuntimeException обекта. Променливата cause може би null Определете нейния тип и я преобразувайте в проверен тип изключение. |
4. Прихващане на множество изключения
Програмистите наистина мразят да дублират code. Те дори излязоха със съответния принцип на развитие: Не се повтаряй (СУХО) . Но когато се обработват изключения, има чести случаи, когато един try
блок е последван от няколко catch
блока със същия code.
Или може да има 3 catch
блока със същия code и още 2 catch
блока с друг идентичен code. Това е стандартна ситуация, когато вашият проект обработва изключенията отговорно.
Започвайки с version 7, в езика Java е добавена възможността за указване на множество типове изключения в един catch
блок. Изглежда нещо подобно:
try
{
// Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
// Exception handling code
}
Можете да имате толкова catch
блокове, колкото искате. Един catch
блок обаче не може да указва изключения, които се наследяват едно друго. С други думи, не можете да напишете catch ( Exception
| RuntimeException
e), защото RuntimeException
класът наследява Exception
.
5. Персонализирани изключения
Винаги можете да създадете свой собствен клас изключение. Вие просто създавате клас, който наследява RuntimeException
класа. Ще изглежда нещо подобно:
class ClassName extends RuntimeException
{
}
Ще обсъдим подробностите, докато научите ООП, наследяване, конструктори и замяна на методи.
Въпреки това, дори ако имате само прост клас като този (изцяло без code), пак можете да хвърляте изключения въз основа на него:
Код | Забележка |
---|---|
|
Хвърлете непроверено MyException . |
В търсенето на Java Multithreading ще се потопим дълбоко в работата с нашите собствени персонализирани изключения.
GO TO FULL VERSION