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 категории: проверени и непроверени .

Всички изключения, които наследяват RuntimeExceptionor Errorсе считат за непроверени изключения . Всички останали са проверени изключения .

важно!

Двадесет години след като бяха въведени проверените изключения, почти всеки Java програмист смята това за грешка. В популярните съвременни рамки 95% от всички изключения не са отметнати. Езикът C#, който почти точно копира Java, не добави проверени изключения .

Каква е основната разлика между проверените и непроверените изключения?

Има допълнителни изисквания, наложени на проверените изключения. Грубо казано, те са следните:

Изискване 1

Ако даден метод хвърля проверено изключение , той трябва да посочи типа изключение в своя подпис . По този начин всеки метод, който го извиква, е наясно, че това "смислено изключение" може да възникне в него.

Посочете проверените изключения след параметрите на метода след throwsключовата дума (не използвайте throwключовата дума по погрешка). Изглежда нещо подобно:

type method (parameters) throws exception

Пример:

проверено изключение непроверено изключение
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

В примера вдясно, нашият 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 да се справим с тях, тогава всеки, който може да извика нашия метод, трябва да бъде уведомен, че такива изключения могат да възникнат в него.

Пример:

Представете си, че пишем метод за създаване на свят, населен с хора. Първоначалният брой хора се предава като аргумент. Така че трябва да добавим изключения, ако има твърде малко хора.

Създаване на Земята Забележка
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
Методът потенциално хвърля две проверени изключения:

  • EmptyWorldException
  • LonelyWorldException

Това извикване на метод може да се обработва по 3 начина:

1. Не хващайте ниHowви изключения

Това най-често се прави, когато методът не знае How правилно да се справи със ситуацията.

Код Забележка
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
Извикващият метод не улавя изключенията и трябва да информира другите за тях: той ги добавя към собствената си throwsклауза

2. Хванете някои от изключенията

Ние се справяме с грешките, с които можем да се справим. Но тези, които не разбираме, ги хвърляме към извикващия метод. За да направим това, трябва да добавим името им към клаузата throws:

Код Забележка
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
Обаждащият се улавя само едно проверено изключение — LonelyWorldException. Другото изключение трябва да се добави към сигнатурата му, като се посочи след throwsключовата дума

3. Уловете всички изключения

Ако методът не хвърля изключения към извикващия метод, тогава извикващият метод винаги е уверен, че всичко е работило добре. И няма да може да предприеме ниHowви действия за коригиране на извънредни ситуации.

Код Забележка
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
Всички изключения се улавят в този метод. Обаждащият се ще бъде уверен, че всичко е минало добре.


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.

Ако желаете, можете лесно да го извлечете от там. Пример:

Код Забележка
try
{
   // Code where we wrap the checked exception
   // in a RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // Exception handling code goes here
   }
}







Вземете изключението, съхранено вътре в 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| RuntimeExceptione), защото RuntimeExceptionкласът наследява Exception.



5. Персонализирани изключения

Винаги можете да създадете свой собствен клас изключение. Вие просто създавате клас, който наследява RuntimeExceptionкласа. Ще изглежда нещо подобно:

class ClassName extends RuntimeException
{
}

Ще обсъдим подробностите, докато научите ООП, наследяване, конструктори и замяна на методи.

Въпреки това, дори ако имате само прост клас като този (изцяло без code), пак можете да хвърляте изключения въз основа на него:

Код Забележка
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




Хвърлете непроверено MyException .

В търсенето на Java Multithreading ще се потопим дълбоко в работата с нашите собствени персонализирани изключения.