1. Typer av undantag

Typer av undantag

Alla undantag är indelade i 4 typer, som faktiskt är klasser som ärver varandra.

Throwableklass

Basklassen för alla undantag är Throwableklassen. Klassen Throwableinnehåller koden som skriver den aktuella anropsstacken (stackspårning av den aktuella metoden) till en array. Vi kommer att lära oss vad en stackspårning är lite senare.

Kastoperatören kan bara acceptera ett objekt som härrör från klassen . ThrowableOch även om du teoretiskt sett kan skriva kod som throw new Throwable();, brukar ingen göra detta. Huvudsyftet med Throwableklassen är att ha en ensamförälderklass för alla undantag.

Errorklass

Nästa undantagsklass är Errorklassen, som direkt ärver Throwableklassen. Java-maskinen skapar objekt av Errorklassen (och dess avkomlingar) när allvarliga problem har uppstått . Till exempel ett hårdvarufel, otillräckligt minne, etc.

Vanligtvis, som programmerare, finns det inget du kan göra i en situation där ett sådant fel (den typ som en Errorska kastas för) har inträffat i programmet: dessa fel är för allvarliga. Allt du kan göra är att meddela användaren att programmet kraschar och/eller skriva all känd information om felet till programloggen.

Exceptionklass

Klasserna Exceptionoch RuntimeExceptionär för vanliga fel som inträffar vid driften av många metoder. Målet med varje kastat undantag är att fångas av ett catchblock som vet hur det ska hanteras på rätt sätt.

När en metod av någon anledning inte kan slutföra sitt arbete, bör den omedelbart meddela anropsmetoden genom att kasta ett undantag av lämplig typ.

Med andra ord, om en variabel är lika med null, kommer metoden att kasta en NullPointerException. Om de felaktiga argumenten skickades till metoden kommer den att kasta en InvalidArgumentException. Om metoden av misstag delar med noll, kommer den att kasta en ArithmeticException.

RuntimeExceptionklass

RuntimeExceptionsär en delmängd av Exceptions. Vi skulle till och med kunna säga att det RuntimeExceptionär en lätt version av vanliga undantag ( Exception) — färre krav och begränsningar ställs på sådana undantag

Du kommer att lära dig skillnaden mellan Exceptionoch RuntimeExceptionsenare.


2. Throws: markerade undantag

Kastar: markerade undantag

Alla Java-undantag delas in i två kategorier: markerad och omarkerad .

Alla undantag som ärver RuntimeExceptioneller Erroranses vara okontrollerade undantag . Alla andra är markerade undantag .

Viktig!

Tjugo år efter att kontrollerade undantag infördes, ser nästan alla Java-programmerare på detta som en bugg. I populära moderna ramverk är 95 % av alla undantag avmarkerade. C#-språket, som nästan kopierade Java exakt, lade inte till markerade undantag .

Vad är den största skillnaden mellan markerade och omarkerade undantag?

Det finns ytterligare krav på kontrollerade undantag. Grovt sett är de dessa:

Krav 1

Om en metod ger ett markerat undantag måste den ange typen av undantag i sin signatur . På så sätt är varje metod som anropar den medveten om att detta "meningsfulla undantag" kan förekomma i den.

Ange markerade undantag efter metodparametrarna efter throwsnyckelordet (använd inte thrownyckelordet av misstag). Det ser ut ungefär så här:

type method (parameters) throws exception

Exempel:

kontrollerat undantag okontrollerat undantag
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!");
}

I exemplet till höger ger vår kod ett omarkerat undantag - ingen ytterligare åtgärd krävs. I exemplet till vänster ger metoden ett markerat undantag, så throwsnyckelordet läggs till i metodsignaturen tillsammans med typen av undantag.

Om en metod förväntar sig att skapa flera markerade undantag måste alla anges efter throwsnyckelordet, avgränsade med kommatecken. Ordningen är inte viktig. Exempel:

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

Krav 2

Om du anropar en metod som har kontrollerat undantag i sin signatur kan du inte bortse från att den kastar dem.

Du måste antingen fånga alla sådana undantag genom att lägga till catchblock för var och en, eller genom att lägga till dem i en throwssats för din metod.

Det är som om vi säger: " Dessa undantag är så viktiga att vi måste fånga dem. Och om vi inte vet hur vi ska hantera dem, då måste alla som kan anropa vår metod meddelas att sådana undantag kan förekomma i den.

Exempel:

Föreställ dig att vi skriver en metod för att skapa en värld befolkad av människor. Det initiala antalet personer skickas som ett argument. Så vi måste lägga till undantag om det är för få personer.

Skapar jorden Notera
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);
}
Metoden ger potentiellt två markerade undantag:

  • EmptyWorldException
  • LonelyWorldException

Detta metodanrop kan hanteras på tre sätt:

1. Fånga inga undantag

Detta görs oftast när metoden inte vet hur den ska hantera situationen på rätt sätt.

Koda Notera
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
Anropsmetoden fångar inte upp undantagen och måste informera andra om dem: den lägger till dem i sin egen throwsklausul

2. Fånga några av undantagen

Vi hanterar de fel vi kan hantera. Men de vi inte förstår, vi kastar upp dem till anropsmetoden. För att göra detta måste vi lägga till deras namn till throws-satsen:

Koda Notera
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
Den som ringer fångar bara ett markerat undantag — LonelyWorldException. Det andra undantaget måste läggas till i sin signatur, vilket anger det efter throwsnyckelordet

3. Fånga alla undantag

Om metoden inte ger undantag från anropsmetoden, är anropsmetoden alltid säker på att allt fungerade bra. Och det kommer inte att kunna vidta några åtgärder för att fixa en exceptionell situation.

Koda Notera
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
Alla undantag fångas i denna metod. Den som ringer kommer att vara säker på att allt gick bra.


3. Undantag för inpackning

Kontrollerade undantag verkade coolt i teorin, men visade sig vara en enorm frustration i praktiken.

Anta att du har en superpopulär metod i ditt projekt. Det kallas från hundratals platser i ditt program. Och du bestämmer dig för att lägga till ett nytt markerat undantag till det. Och det kan mycket väl vara så att detta kontrollerade undantag är riktigt viktigt och så speciellt att bara main()metoden vet vad den ska göra om den fångas.

Det betyder att du måste lägga till det markerade undantaget till throwsklausulen för varje metod som anropar din superpopulära metod . Samt i throwsklausulen av alla metoder som kallar dessa metoder. Och av metoderna som kallar dessa metoder.

Som ett resultat throwsfår klausulerna i hälften av metoderna i projektet ett nytt kontrollerat undantag. Och naturligtvis omfattas ditt projekt av tester, och nu kompileras inte testerna. Och nu måste du redigera kastsatserna i dina tester också.

Och sedan måste all din kod (alla ändringar i hundratals filer) granskas av andra programmerare. Och vid det här laget frågar vi oss själva varför vi gjorde så många blodiga förändringar i projektet? Arbetsdag(ar?) och trasiga tester - allt för att lägga till ett markerat undantag?

Och naturligtvis finns det fortfarande problem relaterade till arv och metodöverstyrning. Problemen som kommer från kontrollerade undantag är mycket större än nyttan. Summan av kardemumman är att nu är få människor som älskar dem och få människor använder dem.

Det finns dock fortfarande mycket kod (inklusive standard Java-bibliotekskod) som innehåller dessa markerade undantag. Vad ska man göra med dem? Vi kan inte ignorera dem, och vi vet inte hur vi ska hantera dem.

Java-programmerare föreslog att linda in kontrollerade undantag i RuntimeException. Med andra ord, fånga alla markerade undantag och skapa sedan omarkerade undantag (till exempel ) RuntimeExceptionoch kasta dem istället. Att göra det ser ut ungefär så här:

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

Det är inte en särskilt snygg lösning, men det finns inget brottsligt här: undantaget var helt enkelt stoppat i en RuntimeException.

Om så önskas kan du enkelt hämta den därifrån. Exempel:

Koda Notera
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
   }
}







Få undantaget lagrat inuti RuntimeExceptionobjektet. Variabeln causekanske null

Bestäm dess typ och konvertera den till en markerad undantagstyp.


4. Fånga flera undantag

Programmerare hatar verkligen att duplicera kod. De kom till och med på en motsvarande utvecklingsprincip: Don't Repeat Yourself (DRY) . Men vid hantering av undantag finns det ofta tillfällen då ett tryblock följs av flera catchblock med samma kod.

Eller det kan finnas 3 catchblock med samma kod och ytterligare 2 catchblock med annan identisk kod. Detta är en standardsituation när ditt projekt hanterar undantag på ett ansvarsfullt sätt.

Från och med version 7 lades i Java-språket till möjligheten att specificera flera typer av undantag i ett enda catchblock. Det ser ut ungefär så här:

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

Du kan ha hur många catchblock du vill. Ett enda catchblock kan dock inte specificera undantag som ärver varandra. Du kan med andra ord inte skriva catch ( Exception| RuntimeExceptione), eftersom RuntimeExceptionklassen ärver Exception.



5. Anpassade undantag

Du kan alltid skapa din egen undantagsklass. Du skapar helt enkelt en klass som ärver RuntimeExceptionklassen. Det kommer att se ut ungefär så här:

class ClassName extends RuntimeException
{
}

Vi kommer att diskutera detaljerna när du lär dig OOP, arv, konstruktörer och metodöverstyrning.

Men även om du bara har en enkel klass som denna (helt utan kod), kan du fortfarande kasta undantag baserat på den:

Koda Notera
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




Kasta en omarkerad MyException .

I Java Multithreading- uppdraget kommer vi att ta en djupdykning i att arbeta med våra egna anpassade undantag.