1. Tipuri de excepții

Tipuri de excepții

Toate excepțiile sunt împărțite în 4 tipuri, care sunt de fapt clase care se moștenesc unele pe altele.

Throwableclasă

Clasa de bază pentru toate excepțiile este Throwableclasa. Clasa Throwableconține codul care scrie stiva de apeluri curentă (urma stivă a metodei curente) într-o matrice. Vom afla ce este o urmă de stivă puțin mai târziu.

Operatorul de aruncare poate accepta doar un obiect care derivă din Throwableclasă. Și, deși teoretic poți scrie cod ca throw new Throwable();, nimeni nu face asta de obicei. Scopul principal al Throwableclasei este de a avea o singură clasă părinte pentru toate excepțiile.

Errorclasă

Următoarea clasă de excepție este Errorclasa, care moștenește direct Throwableclasa. Mașina Java creează obiecte ale Errorclasei (și descendenții acesteia) atunci când au apărut probleme grave . De exemplu, o defecțiune hardware, memorie insuficientă etc.

De obicei, ca programator, nu poți face nimic într-o situație în care o astfel de eroare (de tipul pentru care Errorar trebui aruncat) a apărut în program: aceste erori sunt prea grave. Tot ce puteți face este să notificați utilizatorul că programul se blochează și/sau să scrieți toate informațiile cunoscute despre eroare în jurnalul programului.

Exceptionclasă

Clasele Exceptionși RuntimeExceptionsunt pentru erori obișnuite care apar în operarea multor metode. Scopul fiecărei excepții aruncate este să fie prins de un catchbloc care știe să o gestioneze corect.

Atunci când o metodă nu își poate finaliza activitatea dintr-un motiv oarecare, ar trebui să notifice imediat metoda de apelare, lansând o excepție de tipul adecvat.

Cu alte cuvinte, dacă o variabilă este egală cu null, metoda va arunca un NullPointerException. Dacă argumentele incorecte au fost transmise metodei, aceasta va arunca un InvalidArgumentException. Dacă metoda se împarte accidental la zero, va arunca un ArithmeticException.

RuntimeExceptionclasă

RuntimeExceptionssunt un subset al Exceptions. Am putea spune chiar că RuntimeExceptioneste o versiune ușoară a excepțiilor obișnuite ( Exception) — sunt impuse mai puține cerințe și restricții pentru astfel de excepții

Veți învăța diferența dintre Exceptionși RuntimeExceptionmai târziu.


2. Throws: excepții verificate

Aruncări: excepții bifate

Toate excepțiile Java se încadrează în 2 categorii: bifate și nebifate .

Toate excepțiile care moștenesc RuntimeExceptionsau Errorsunt considerate excepții neverificate . Toate celelalte sunt excepții bifate .

Important!

La douăzeci de ani după ce au fost introduse excepții verificate, aproape fiecare programator Java consideră asta ca pe o eroare. În cadrele moderne populare, 95% din toate excepțiile sunt neverificate. Limbajul C#, care aproape a copiat Java exact, nu a adăugat excepții bifate .

Care este principala diferență dintre excepțiile bifate și neverificate ?

Există cerințe suplimentare impuse excepțiilor verificate . În linii mari, acestea sunt acestea:

Cerința 1

Dacă o metodă aruncă o excepție bifată , trebuie să indice tipul de excepție în semnătura sa . În acest fel, fiecare metodă care o apelează este conștientă de faptul că această „excepție semnificativă” ar putea apărea în ea.

Indicați excepțiile bifate după parametrii metodei după throwscuvântul cheie (nu utilizați throwcuvântul cheie din greșeală). Arata cam asa:

type method (parameters) throws exception

Exemplu:

excepție verificată excepție nebifată
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!");
}

În exemplul din dreapta, codul nostru aruncă o excepție nebifată - nu este necesară nicio acțiune suplimentară. În exemplul din stânga, metoda aruncă o excepție bifatăthrows , astfel încât cuvântul cheie este adăugat la semnătura metodei împreună cu tipul excepției.

Dacă o metodă se așteaptă să arunce mai multe excepții bifate , toate acestea trebuie specificate după throwscuvântul cheie, separate prin virgule. Ordinea nu este importantă. Exemplu:

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");
}

Cerința 2

Dacă apelați o metodă care a verificat excepții în semnătură, nu puteți ignora faptul că le aruncă.

Trebuie fie să prindeți toate astfel de excepții adăugând catchblocuri pentru fiecare, fie adăugându-le la o throwsclauză pentru metoda dvs.

Este ca și cum am spune: „ Aceste excepții sunt atât de importante încât trebuie să le prindem. Și dacă nu știm cum să le gestionăm, atunci oricine ar putea apela metoda noastră trebuie să fie anunțat că astfel de excepții pot apărea în ea.

Exemplu:

Imaginați-vă că scriem o metodă pentru a crea o lume populată de oameni. Numărul inițial de persoane este trecut ca argument. Deci trebuie să adăugăm excepții dacă sunt prea puțini oameni.

Crearea Pământului Notă
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);
}
Metoda poate arunca două excepții verificate :

  • EmptyWorldException
  • LonelyWorldException

Acest apel de metodă poate fi gestionat în 3 moduri:

1. Nu prinde nicio excepție

Acest lucru se face cel mai adesea atunci când metoda nu știe cum să gestioneze corect situația.

Cod Notă
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
Metoda de apelare nu prinde excepțiile și trebuie să informeze pe alții despre ele: le adaugă la propria throwsclauză

2. Prinde unele dintre excepții

Ne ocupăm de erorile pe care le putem gestiona. Dar pe cei pe care nu îi înțelegem, îi aruncăm până la metoda de chemare. Pentru a face acest lucru, trebuie să adăugăm numele lor la clauza throws:

Cod Notă
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
Apelantul prinde doar o excepție bifatăLonelyWorldException — . Cealaltă excepție trebuie adăugată la semnătura acesteia, indicând-o după throwscuvântul cheie

3. Prinde toate excepțiile

Dacă metoda nu aruncă excepții de la metoda de apelare, atunci metoda de apelare este întotdeauna sigură că totul a funcționat bine. Și nu va putea lua nicio măsură pentru a remedia situații excepționale.

Cod Notă
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
Toate excepțiile sunt prinse în această metodă. Apelantul va fi încrezător că totul a mers bine.


3. Excepții de împachetare

Excepțiile bifate păreau cool în teorie, dar s-au dovedit a fi o mare frustrare în practică.

Să presupunem că aveți o metodă super populară în proiectul dvs. Este apelat din sute de locuri din programul dvs. Și decideți să adăugați o nouă excepție bifată . Și se poate ca această excepție verificată să fie cu adevărat importantă și atât de specială încât numai main()metoda știe ce să facă dacă este prinsă.

Aceasta înseamnă că va trebui să adăugați excepția bifată la throwsclauza fiecărei metode care vă numește metoda super populară . La fel ca și în throwsclauza tuturor metodelor care apelează acele metode. Și a metodelor care numesc acele metode.

Ca urmare, throwsclauzele a jumătate dintre metodele din proiect primesc o nouă excepție verificată . Și, desigur, proiectul tău este acoperit de teste, iar acum testele nu se compilează. Și acum trebuie să editați și clauzele throws din testele dvs.

Și apoi tot codul tău (toate modificările din sute de fișiere) va trebui să fie revizuit de alți programatori. Și în acest moment ne întrebăm de ce am făcut am făcut atâtea schimbări sângeroase în proiect? Zilele de lucru și testele întrerupte - totul de dragul de a adăuga o excepție verificată ?

Și, bineînțeles, există încă probleme legate de moștenire și de anulare a metodei. Problemele care provin din excepțiile verificate sunt mult mai mari decât beneficiul. Concluzia este că acum puțini oameni le iubesc și puțini le folosesc.

Cu toate acestea, există încă o mulțime de cod (inclusiv cod standard de bibliotecă Java) care conține aceste excepții verificate . Ce e de făcut cu ele? Nu le putem ignora și nu știm cum să le gestionăm.

Programatorii Java au propus să încapsuleze excepțiile verificateRuntimeException în . Cu alte cuvinte, prindeți toate excepțiile verificate și apoi creați excepții nebifate (de exemplu, RuntimeException) și aruncați-le în schimb. Făcând asta arată cam așa:

try
{
   // Code where a checked exception might occur
}
catch(Exception exp)
{
   throw new RuntimeException(exp);
}

Nu este o soluție foarte frumoasă, dar nu este nimic criminal aici: excepția a fost pur și simplu îndesată într-un RuntimeException.

Dacă doriți, îl puteți recupera cu ușurință de acolo. Exemplu:

Cod Notă
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
   }
}







Obțineți excepția stocată în interiorul RuntimeExceptionobiectului. Variabila causepoate null

să-și determine tipul și să o convertească într-un tip de excepție verificat .


4. Prinderea mai multor excepții

Programatorii chiar urăsc să dubleze codul. Au venit chiar și cu un principiu de dezvoltare corespunzător: Don’t Repeat Yourself (DRY) . Dar atunci când se gestionează excepții, există ocazii frecvente când un trybloc este urmat de mai multe catchblocuri cu același cod.

Sau ar putea fi 3 catchblocuri cu același cod și alte 2 catchblocuri cu alt cod identic. Aceasta este o situație standard când proiectul dvs. gestionează excepțiile în mod responsabil.

Începând cu versiunea 7, în limbajul Java a adăugat posibilitatea de a specifica mai multe tipuri de excepții într-un singur catchbloc. Arata cam asa:

try
{
   // Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
   // Exception handling code
}

Puteți avea câte catchblocuri doriți. Cu toate acestea, un singur catchbloc nu poate specifica excepții care se moștenesc reciproc. Cu alte cuvinte, nu puteți scrie catch ( Exception| RuntimeExceptione), deoarece RuntimeExceptionclasa moștenește Exception.



5. Excepții personalizate

Puteți oricând să vă creați propria clasă de excepție. Pur și simplu creați o clasă care moștenește RuntimeExceptionclasa. Va arata cam asa:

class ClassName extends RuntimeException
{
}

Vom discuta detaliile pe măsură ce învățați OOP, moștenirea, constructorii și suprascrierea metodei.

Cu toate acestea, chiar dacă aveți doar o clasă simplă ca aceasta (în întregime fără cod), puteți totuși să aruncați excepții pe baza ei:

Cod Notă
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




Aruncă un necontrolat MyException .

În misiunea Java Multithreading , ne vom aprofunda în lucrul cu propriile noastre excepții personalizate.