Inleiding
Een uitzondering is een foutgebeurtenis die kan optreden tijdens de uitvoering van een programma en de normale gang ervan verstoort. Java biedt een robuuste en objectgeoriënteerde manier om uitzonderingsscenario’s te behandelen, bekend als Java Exception Handling.
Uitzonderingen in Java kunnen voortkomen uit verschillende situaties, zoals verkeerde gegevens ingevoerd door de gebruiker, hardwarestoring, netwerkverbindingstoring of een database-server die niet beschikbaar is. De code die specificeert wat te doen in specifieke uitzonderingsscenario’s, wordt exception handling genoemd.
Het gooien en vangen van uitzonderingen
Java maakt een uitzonderingsobject aan wanneer er een fout optreedt tijdens de uitvoering van een instructie. Het uitzonderingsobject bevat veel debugginformatie, zoals de methodiekhierarchie, het regelnummer waar de uitzondering zich heeft voorgedaan, en het type uitzondering.
Als er een uitzondering optreedt in een methode, wordt het proces van het maken van het uitzonderingsobject en het overdragen ervan aan de runtime-omgeving “het gooien van de uitzondering” genoemd. De normale stroom van het programma stopt en de Java Runtime Environment (JRE) probeert de handler voor de uitzondering te vinden. De uitzonderingsafhandelaar is het blok code dat het uitzonderingsobject kan verwerken.
- De logica om de uitzonderingsafhandelaar te vinden begint met zoeken in de methode waar de fout is opgetreden.
- Als er geen geschikte handler wordt gevonden, wordt deze verplaatst naar de aanroepende methode.
- En zo verder.
Dus als de methodeoproepstack A->B->C
is en er doet zich een uitzondering voor in methode C
, dan zal de zoektocht naar de juiste handler verplaatsen van C->B->A
.
Als er een geschikte uitzonderingsafhandelaar is gevonden, wordt het uitzonderingsobject doorgegeven aan de handler om het te verwerken. De handler wordt gezegd “de uitzondering op te vangen”. Als er geen geschikte uitzonderingsafhandelaar is gevonden, wordt het programma beëindigd en worden er gegevens over de uitzondering naar de console afgedrukt.
Het Java-uitzonderingsafhandelingsframework wordt alleen gebruikt om runtime-fouten af te handelen. De compileerfouten moeten worden opgelost door de ontwikkelaar die de code schrijft, anders wordt het programma niet uitgevoerd.
Java-uitzonderingsafhandelingssleutelwoorden
Java biedt specifieke trefwoorden voor het hanteren van uitzonderingen.
- throw – We weten dat als er een fout optreedt, er een uitzonderingsobject wordt gemaakt en vervolgens de Java-runtime begint met het verwerken ervan. Soms willen we mogelijk expliciet uitzonderingen genereren in onze code. Bijvoorbeeld, in een programma voor gebruikersauthenticatie moeten we uitzonderingen naar clients gooien als het wachtwoord
null
is. Het trefwoordthrow
wordt gebruikt om uitzonderingen naar de runtime te gooien om ze te laten afhandelen. - throws – Wanneer we een uitzondering gooien in een methode en deze niet afhandelen, moeten we het trefwoord
throws
gebruiken in de methodehandtekening om het aanroepende programma te laten weten welke uitzonderingen door de methode kunnen worden gegooid. De aanroepende methode kan deze uitzonderingen afhandelen of doorgeven aan zijn aanroepende methode met behulp van het trefwoordthrows
. We kunnen meerdere uitzonderingen opgeven in dethrows
-clausule, en het kan ook worden gebruikt met demain()
-methode. - try-catch – We gebruiken het
try-catch
-blok voor uitzonderingsafhandeling in onze code.try
is het begin van het blok encatch
staat aan het einde van hettry
-blok om de uitzonderingen af te handelen. We kunnen meerderecatch
-blokken hebben met eentry
-blok. Hettry-catch
-blok kan ook genest zijn. Hetcatch
-blok vereist een parameter die van het typeException
moet zijn. - eindelijk – het
eindelijk
blok is optioneel en kan alleen worden gebruikt met eentry-catch
blok. Aangezien een uitzondering het uitvoeringsproces onderbreekt, kunnen we enkele bronnen open hebben die niet worden gesloten, dus kunnen we heteindelijk
blok gebruiken. Heteindelijk
blok wordt altijd uitgevoerd, of er nu een uitzondering is opgetreden of niet.
Een Voorbeeld van Uitzonderingsafhandeling
- De methode
testException()
gooit uitzonderingen met behulp van hetthrow
trefwoord. De methodehandtekening gebruikt hetthrows
trefwoord om de beller te laten weten welk type uitzonderingen het zou kunnen gooien. - In de
main()
methode handel ik uitzonderingen af met behulp van hettry-catch
blok in demain()
methode. Wanneer ik het niet afhandel, propageer ik het naar runtime met dethrows
clausule in demain()
methode. - De
testException(-10)
wordt nooit uitgevoerd vanwege de uitzondering en vervolgens wordt heteindelijk
blok uitgevoerd.
De printStackTrace()
is een van de nuttige methoden in de Exception
klasse voor debugdoeleinden.
Deze code geeft het volgende uit:
Outputjava.io.FileNotFoundException: Negative Integer -5
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
Enkele belangrijke punten om op te merken:
- We kunnen geen
catch
ofeindelijk
clausule hebben zonder eentry
verklaring. - A
try
statement should have eithercatch
block orfinally
block, it can have both blocks. - We kunnen geen code schrijven tussen
try-catch-finally
-blokken. - We kunnen meerdere
catch
-blokken hebben met ééntry
-verklaring. try-catch
-blokken kunnen genest zijn, net alsif-else
-verklaringen.- We kunnen slechts één
finally
-blok hebben met eentry-catch
-verklaring.
Java Exception-hiërarchie
Zoals eerder vermeld, wordt bij het genereren van een uitzondering een uitzonderingsobject gemaakt. Java-uitzonderingen zijn hiërarchisch en overerving wordt gebruikt om verschillende soorten uitzonderingen te categoriseren. Throwable
is de bovenliggende klasse van de Java Exception-hiërarchie en heeft twee onderliggende objecten – Error
en Exception
. Exception
s zijn verder onderverdeeld in gecontroleerde Exception
s en runtime-Exception
s.
- Fouten:
Error
s zijn uitzonderlijke scenario’s die buiten de reikwijdte van de toepassing vallen en waarvan het niet mogelijk is ze te voorzien en te herstellen. Bijvoorbeeld hardwarefouten, Java Virtual Machine (JVM)-crash of out-of-memory fout. Daarom hebben we een aparte hiërarchie vanError
s en zouden we niet moeten proberen deze situaties af te handelen. Enkele veelvoorkomendeError
s zijnOutOfMemoryError
enStackOverflowError
. - Gecontroleerde Uitzonderingen: Gecontroleerde
Exception
s zijn uitzonderingsscenario’s die we kunnen verwachten in een programma en waarvan we proberen te herstellen. Bijvoorbeeld,FileNotFoundException
. We moeten deze uitzondering opvangen en een bruikbaar bericht aan de gebruiker verstrekken en het correct registreren voor debugdoeleinden. DeException
is de bovenliggende klasse van alle gecontroleerdeException
s. Als we een gecontroleerdeException
gooien, moeten we deze opvangen in dezelfde methode, of we moeten deze doorgeven aan de beller met behulp van hetthrows
-trefwoord. - Uitzondering tijdens uitvoering: Uitzonderingen tijdens uitvoering worden veroorzaakt door slechte programmering. Bijvoorbeeld, proberen een element uit een array op te halen. We moeten eerst de lengte van de array controleren voordat we proberen het element op te halen, anders kan het een
ArrayIndexOutOfBoundException
veroorzaken tijdens de uitvoering.RuntimeException
is de bovenliggende klasse van alle uitzonderingen tijdens uitvoering. Als we in een methode een willekeurigeException
gooien, is het niet vereist om deze te specificeren in de methodeondertekeningthrows
-clausule. Uitzonderingen tijdens uitvoering kunnen worden vermeden met betere programmering.
Enkele handige methoden van Exception-klassen
Java Exception
en al zijn subklassen bieden geen specifieke methoden, en alle methoden zijn gedefinieerd in de basisklasse – Throwable
. De klassen Exception
worden gemaakt om verschillende soorten Exception
-scenario’s te specificeren, zodat we gemakkelijk de oorzaak kunnen identificeren en de Exception
kunnen afhandelen volgens het type ervan. De klasse Throwable
implementeert de interface Serializable
voor interoperabiliteit.
Enkele van de bruikbare methoden van de klasse Throwable
zijn:
- public String getMessage() – Deze methode retourneert de berichttekst van
Throwable
en het bericht kan worden opgegeven bij het maken van de uitzondering via de constructor. - public String getLocalizedMessage() – Deze methode wordt geleverd zodat subklassen deze kunnen overschrijven om een taalspecifiek bericht te leveren aan het aanroepende programma. De implementatie van de klasse
Throwable
van deze methode gebruikt de methodegetMessage()
om het uitzonderingsbericht te retourneren. - public synchronized Throwable getCause() – Deze methode retourneert de oorzaak van de uitzondering of
null
als de oorzaak onbekend is. - public String toString() – Deze methode retourneert informatie over
Throwable
inString
-formaat, de geretourneerdeString
bevat de naam van de klasseThrowable
en het gelokaliseerde bericht. - public void printStackTrace() – Deze methode drukt de stack trace-informatie af naar de standaardfoutstroom, deze methode is overbelast, en we kunnen
PrintStream
ofPrintWriter
als argument doorgeven om de stack trace-informatie naar het bestand of de stroom te schrijven.
Java 7 Automatisch Resourcebeheer en Catch-blokverbeteringen
Als je veel uitzonderingen catch
t in een enkel try
-blok, zul je merken dat de code van het catch
-blok voornamelijk bestaat uit redundante code om de fout te loggen. In Java 7 was een van de functies een verbeterd catch
-blok waar we meerdere uitzonderingen kunnen catch
en in een enkel catch
-blok. Hier is een voorbeeld van het catch
-blok met deze functie:
Er zijn enkele beperkingen zoals dat het uitzonderingsobject definitief is en we het niet kunnen wijzigen binnen het catch
-blok, lees de volledige analyse op Java 7 Catch Block Improvements.
Meestal gebruiken we het finally
-blok alleen om de resources te sluiten. Soms vergeten we ze te sluiten en krijgen we runtime-uitzonderingen wanneer de resources zijn uitgeput. Deze uitzonderingen zijn moeilijk te debuggen, en we moeten misschien op elke plaats waar we die resource gebruiken controleren of we hem sluiten. In Java 7 was een van de verbeteringen try-with-resources
, waar we een resource in de try
-verklaring zelf kunnen maken en het binnen het try-catch
-blok kunnen gebruiken. Wanneer de uitvoering uit het try-catch
-blok komt, sluit de runtime-omgeving deze resources automatisch. Hier is een voorbeeld van het try-catch
-blok met deze verbetering:
A Custom Exception Class Example
Java biedt ons veel uitzonderingsklassen om te gebruiken, maar soms moeten we onze eigen aangepaste uitzonderingsklassen maken. Bijvoorbeeld, om de aanroeper op de hoogte te stellen van een specifiek type uitzondering met de juiste boodschap. We kunnen aangepaste velden hebben voor het bijhouden, zoals foutcodes. Stel bijvoorbeeld dat we een methode schrijven om alleen tekstbestanden te verwerken, dan kunnen we de aanroeper de juiste foutcode geven wanneer een ander type bestand als invoer wordt verzonden.
Eerst, maak MyException
:
Vervolgens, maak een CustomExceptionExample
:
We kunnen een aparte methode hebben om verschillende soorten foutcodes te verwerken die we krijgen van verschillende methoden. Sommige ervan worden verbruikt omdat we de gebruiker daar misschien niet van op de hoogte willen stellen, of sommige ervan gooien we terug om de gebruiker op de hoogte te stellen van het probleem.
Hier breid ik Exception
uit, zodat wanneer deze uitzondering wordt gegenereerd, deze moet worden afgehandeld in de methode of teruggegeven aan het aanroepende programma. Als we RuntimeException
uitbreiden, is het niet nodig om dit te specificeren in de throws
-clausule.
Dit was een ontwerpbeslissing. Het gebruik van Gecontroleerde Exception
s heeft het voordeel dat het ontwikkelaars helpt te begrijpen welke uitzonderingen ze kunnen verwachten en passende maatregelen te nemen om ze af te handelen.
Best Practices voor Exception Handling in Java
- Gebruik Specifieke Uitzonderingen – Basis klassen van de uitzonderingshiërarchie bieden geen nuttige informatie, daarom heeft Java zoveel uitzonderingsklassen, zoals
IOException
met verdere subklassen zoalsFileNotFoundException
,EOFException
, enzovoort. We moeten altijd specifieke uitzonderingsklassenthrow
encatch
zodat de aanroeper gemakkelijk de hoofdoorzaak van de uitzondering zal kennen en ze kan verwerken. Dit maakt het debuggen eenvoudiger en helpt clienttoepassingen uitzonderingen op de juiste manier af te handelen. - Goed vroeg of Falen-Snel – We moeten proberen uitzonderingen zo vroeg mogelijk te
throw
. Neem de bovenstaandeprocessFile()
methode, als we hetnull
argument aan deze methode doorgeven, krijgen we de volgende uitzondering:
OutputException in thread "main" java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:134)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
Tijdens het debuggen moeten we goed naar de stack trace kijken om de werkelijke locatie van de uitzondering te identificeren. Als we onze implementatielogica wijzigen om deze uitzonderingen vroegtijdig te controleren zoals hieronder:
dant zal de uitzonderingsstack trace aangeven waar de uitzondering is opgetreden met een duidelijke boodschap:
Outputcom.journaldev.exceptions.MyException: File name can't be null
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
- Vang Laat – Omdat Java afdwingt om de gecontroleerde uitzondering te behandelen of deze te verklaren in de methodehandtekening, hebben ontwikkelaars soms de neiging om de uitzondering op te vangen en de fout te loggen. Maar deze praktijk is schadelijk omdat het aanroepende programma geen melding krijgt van de uitzondering. We moeten uitzonderingen alleen opvangen als we ze op de juiste manier kunnen afhandelen. Bijvoorbeeld, in de bovenstaande methode gooi ik uitzonderingen terug naar de aanroepende methode om ze te laten afhandelen. Dezelfde methode kan worden gebruikt door andere toepassingen die de uitzondering mogelijk op een andere manier willen verwerken. Bij het implementeren van een functie moeten we altijd uitzonderingen terug gooien naar de aanroeper en hen laten beslissen hoe ze ermee om moeten gaan.
- Resources Sluiten – Omdat uitzonderingen de verwerking van het programma stoppen, moeten we alle bronnen sluiten in een finally-blok of de Java 7
try-with-resources
-verbetering gebruiken om Java-runtime deze voor u te laten sluiten. - Loggen van Uitzonderingen – We zouden altijd uitzonderingsberichten moeten loggen en bij het
gooien
van uitzonderingen een duidelijk bericht moeten geven, zodat de beller gemakkelijk weet waarom de uitzondering is opgetreden. We zouden altijd een leegcatch
-blok moeten vermijden dat alleen de uitzondering opvangt en geen zinvolle details van de uitzondering biedt voor het debuggen. - Enkelvoudig catch-blok voor meerdere uitzonderingen – Meestal loggen we uitzonderingsdetails en geven we een bericht aan de gebruiker, in dit geval moeten we de Java 7-functie gebruiken voor het afhandelen van meerdere uitzonderingen in een enkel
catch
-blok. Deze aanpak zal onze codeomvang verminderen en het zal er ook schoner uitzien. - Het Gebruik van Aangepaste Uitzonderingen – Het is altijd beter om een uitzonderingsafhandelingsstrategie te definiëren bij het ontwerpen en in plaats van meerdere uitzonderingen te
gooien
en tevangen
, kunnen we een aangepaste uitzondering maken met een foutcode, en het aanroepende programma kan deze foutcodes afhandelen. Het is ook een goed idee om een hulpprogramma-methode te maken om verschillende foutcodes te verwerken en ze te gebruiken. - Naamgevingsconventies en Verpakking – Wanneer je je aangepaste uitzondering maakt, zorg er dan voor dat het eindigt met
Exception
, zodat het vanuit de naam zelf duidelijk is dat het een uitzonderingsklasse is. Zorg er ook voor dat je ze verpakt zoals dat gebeurt in de Java Development Kit (JDK). Bijvoorbeeld,IOException
is de basissuggestie voor alle IO-operaties. - Gebruik Uitzonderingen Verstandig – Uitzonderingen zijn kostbaar, en soms is het helemaal niet nodig om uitzonderingen te gooien, en kunnen we een boolean variabele teruggeven aan het aanroepende programma om aan te geven of een bewerking succesvol was of niet. Dit is handig wanneer de bewerking optioneel is, en je wilt niet dat je programma vastloopt omdat het mislukt. Bijvoorbeeld, tijdens het bijwerken van de aandelenkoersen in de database vanuit een webservice van een derde partij, willen we misschien vermijden uitzonderingen te gooien als de verbinding mislukt.
- Documenteer de Geworpen Uitzonderingen – Gebruik Javadoc
@throws
om duidelijk de uitzonderingen aan te geven die door de methode worden gegooid. Het is zeer nuttig wanneer je een interface aanbiedt voor andere applicaties om te gebruiken.
Conclusie
In dit artikel heb je geleerd over uitzonderingsafhandeling in Java. Je hebt geleerd over throw
en throws
. Je hebt ook geleerd over try
(en try-with-resources
), catch
, en finally
blokken.
Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java