1. Typer av undantag
Alla undantag är indelade i 4 typer, som faktiskt är klasser som ärver varandra.
Throwable
klass
Basklassen för alla undantag är Throwable
klassen. Klassen Throwable
innehå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 . Throwable
Och även om du teoretiskt sett kan skriva kod som throw new Throwable();
, brukar ingen göra detta. Huvudsyftet med Throwable
klassen är att ha en ensamförälderklass för alla undantag.
Error
klass
Nästa undantagsklass är Error
klassen, som direkt ärver Throwable
klassen. Java-maskinen skapar objekt av Error
klassen (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 Error
ska 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.
Exception
klass
Klasserna Exception
och 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 catch
block 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
.
RuntimeException
klass
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 Exception
och RuntimeException
senare.
2. Throws
: markerade undantag
Alla Java-undantag delas in i två kategorier: markerad och omarkerad .
Alla undantag som ärver RuntimeException
eller Error
anses vara okontrollerade undantag . Alla andra är markerade undantag .
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 throws
nyckelordet (använd inte throw
nyckelordet av misstag). Det ser ut ungefär så här:
type method (parameters) throws exception
Exempel:
kontrollerat undantag | okontrollerat undantag |
---|---|
|
|
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å throws
nyckelordet 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 throws
nyckelordet, 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 catch
block för var och en, eller genom att lägga till dem i en throws
sats 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 |
---|---|
|
Metoden ger potentiellt två markerade undantag:
|
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 |
---|---|
|
Anropsmetoden fångar inte upp undantagen och måste informera andra om dem: den lägger till dem i sin egen throws klausul |
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 |
---|---|
|
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 throws nyckelordet |
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 |
---|---|
|
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 throws
klausulen för varje metod som anropar din superpopulära metod . Samt i throws
klausulen av alla metoder som kallar dessa metoder. Och av metoderna som kallar dessa metoder.
Som ett resultat throws
få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 ) RuntimeException
och 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 |
---|---|
|
Få undantaget lagrat inuti RuntimeException objektet. Variabeln cause kanske 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 try
block följs av flera catch
block med samma kod.
Eller det kan finnas 3 catch
block med samma kod och ytterligare 2 catch
block 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 catch
block. 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 catch
block du vill. Ett enda catch
block kan dock inte specificera undantag som ärver varandra. Du kan med andra ord inte skriva catch ( Exception
| RuntimeException
e), eftersom RuntimeException
klassen ärver Exception
.
5. Anpassade undantag
Du kan alltid skapa din egen undantagsklass. Du skapar helt enkelt en klass som ärver RuntimeException
klassen. 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 |
---|---|
|
Kasta en omarkerad MyException . |
I Java Multithreading- uppdraget kommer vi att ta en djupdykning i att arbeta med våra egna anpassade undantag.
GO TO FULL VERSION