Uitzonderingen in Java, deel 1: Basisprincipes van het afhandelen van uitzonderingen

Java-uitzonderingen zijn bibliotheektypen en taalfuncties die worden gebruikt om programmafouten te vertegenwoordigen en aan te pakken. Als u wilt begrijpen hoe mislukking wordt weergegeven in de broncode, bent u hier aan het juiste adres. Naast een overzicht van Java-uitzonderingen, zal ik je op weg helpen met Java's taalfuncties voor het gooien van objecten, het proberen van code die mogelijk mislukt, het opvangen van gegooide objecten en het opschonen van je Java-code nadat er een uitzondering is gegenereerd.

In de eerste helft van deze tutorial leer je over basistaalfuncties en bibliotheektypes die al bestaan ​​sinds Java 1.0. In de tweede helft ontdekt u geavanceerde mogelijkheden die zijn geïntroduceerd in recentere Java-versies.

Merk op dat codevoorbeelden in deze tutorial compatibel zijn met JDK 12.

download Download de code Download de broncode voor voorbeeldtoepassingen in deze tutorial. Gemaakt door Jeff Friesen voor JavaWorld.

Wat zijn Java-uitzonderingen?

Er treedt een fout op wanneer het normale gedrag van een Java-programma wordt onderbroken door onverwacht gedrag. Dit verschil staat bekend als een uitzondering . Een programma probeert bijvoorbeeld een bestand te openen om de inhoud te lezen, maar het bestand bestaat niet. Java classificeert uitzonderingen in een paar typen, dus laten we ze allemaal bekijken.

Uitzonderingen gecontroleerd

Java classificeert uitzonderingen die voortkomen uit externe factoren (zoals een ontbrekend bestand) als aangevinkte uitzonderingen . De Java-compiler controleert of dergelijke uitzonderingen worden afgehandeld (gecorrigeerd) waar ze voorkomen of worden gedocumenteerd om elders te worden afgehandeld.

Uitzonderingshandlers

Een exception-handler is een reeks code die een exception afhandelt. Het ondervraagt ​​de context - wat betekent dat het waarden leest die zijn opgeslagen uit variabelen die binnen het bereik waren op het moment dat de uitzondering optrad - en gebruikt vervolgens wat het leert om het Java-programma te herstellen naar een stroom van normaal gedrag. Een uitzonderingshandler kan bijvoorbeeld een opgeslagen bestandsnaam lezen en de gebruiker vragen het ontbrekende bestand te vervangen.

Runtime (niet aangevinkte) uitzonderingen

Stel dat een programma probeert een geheel getal te delen door een geheel getal 0. Deze onmogelijkheid illustreert een ander soort uitzondering, namelijk een runtime-uitzondering . In tegenstelling tot aangevinkte uitzonderingen, ontstaan ​​uitzonderingen tijdens runtime meestal door slecht geschreven broncode, en moeten daarom door de programmeur worden opgelost. Omdat de compiler niet controleert of runtime-uitzonderingen worden afgehandeld of gedocumenteerd om elders te worden afgehandeld, kunt u een runtime-uitzondering beschouwen als een niet-gecontroleerde uitzondering .

Over runtime-uitzonderingen

U kunt een programma aanpassen om een ​​runtime-uitzondering af te handelen, maar het is beter om de broncode te repareren. Runtime-uitzonderingen ontstaan ​​vaak door ongeldige argumenten door te geven aan de methoden van een bibliotheek; de belcode met fouten moet worden opgelost.

Fouten

Sommige uitzonderingen zijn zeer ernstig omdat ze de uitvoering van een programma in gevaar brengen. Een programma probeert bijvoorbeeld geheugen van de JVM toe te wijzen, maar er is niet genoeg vrij geheugen om aan het verzoek te voldoen. Een andere ernstige situatie doet zich voor wanneer een programma een classfile probeert te laden via een methodeaanroep Class.forName(), maar het classfile is beschadigd. Dit soort uitzondering wordt een fout genoemd . Probeer nooit zelf fouten op te lossen, omdat de JVM er mogelijk niet van kan herstellen.

Uitzonderingen in broncode

Een uitzondering kan in de broncode worden weergegeven als een foutcode of als een object . Ik zal beide introduceren en je laten zien waarom objecten superieur zijn.

Foutcodes versus objecten

Programmeertalen zoals C gebruiken integer-gebaseerde foutcodes om fouten en redenen voor mislukking aan te geven, dwz uitzonderingen. Hier zijn een paar voorbeelden:

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

De functie van C chdir()(directory wijzigen) retourneert een geheel getal: 0 bij succes of -1 bij mislukking. Evenzo fopen()retourneert de functie van C (bestand geopend) een niet-volledige aanwijzer (adres met een geheel getal) naar een FILEstructuur bij succes of een nul (0) aanwijzer (weergegeven door constante NULL) bij een fout. Om de uitzondering te identificeren die de fout heeft veroorzaakt, moet u in beide gevallen de errnofoutcode van de globale variabele lezen .

Foutcodes geven enkele problemen:

  • Gehele getallen zijn zinloos; ze beschrijven niet de uitzonderingen die ze vertegenwoordigen. Wat betekent bijvoorbeeld 6?
  • Het koppelen van context aan een foutcode is lastig. U wilt bijvoorbeeld de naam uitvoeren van het bestand dat niet kon worden geopend, maar waar gaat u de bestandsnaam opslaan?
  • Gehele getallen zijn willekeurig, wat tot verwarring kan leiden bij het lezen van de broncode. Het specificeren van if (!chdir("C:\\temp"))( !betekent NIET) in plaats van if (chdir("C:\\temp"))testen op fouten is bijvoorbeeld duidelijker. Er is echter 0 gekozen om succes aan te geven en if (chdir("C:\\temp"))moet dus worden opgegeven om te testen op mislukking.
  • Foutcodes zijn te gemakkelijk te negeren, wat kan leiden tot foutcodes. De programmeur zou bijvoorbeeld chdir("C:\\temp");de if (fp == NULL)controle kunnen specificeren en negeren . Bovendien hoeft de programmeur niet te onderzoeken errno. Door niet op fouten te testen, gedraagt ​​het programma zich onregelmatig wanneer een van beide functies een foutindicator retourneert.

Om deze problemen op te lossen, heeft Java een nieuwe benadering van het afhandelen van uitzonderingen omarmd. In Java combineren we objecten die uitzonderingen beschrijven met een mechanisme dat gebaseerd is op het gooien en vangen van deze objecten. Hier zijn enkele voordelen van het gebruik van objecten versus foutcode om uitzonderingen aan te duiden:

  • Een object kan worden gemaakt op basis van een klasse met een betekenisvolle naam. Bijvoorbeeld FileNotFoundException(in het java.iopakket) meer betekenis dan 6.
  • Objecten kunnen context in verschillende velden opslaan. U kunt bijvoorbeeld een bericht, de naam van het bestand dat niet kon worden geopend, de meest recente positie waar een parseerbewerking is mislukt en / of andere items in de velden van een object opslaan.
  • U gebruikt geen ifverklaringen om te testen op mislukking. In plaats daarvan worden exception-objecten naar een handler gestuurd die los staat van de programmacode. Het resultaat is dat de broncode gemakkelijker te lezen is en minder snel fouten bevat.

Gooibaar en zijn subklassen

Java biedt een hiërarchie van klassen die verschillende soorten uitzonderingen vertegenwoordigen. Deze klassen zijn geworteld in het java.langpakket van de Throwableklas, samen met haar Exception, RuntimeExceptionen Errorsubklassen.

Throwableis de ultieme superklasse als het om uitzonderingen gaat. Alleen objecten die zijn gemaakt van Throwableen de bijbehorende subklassen kunnen worden gegooid (en vervolgens worden opgevangen). Dergelijke objecten staan ​​bekend als throwables .

Een Throwableobject is gekoppeld aan een detailbericht dat een uitzondering beschrijft. Er zijn verschillende constructeurs beschikbaar, waaronder het paar dat hieronder wordt beschreven, om een Throwableobject te maken met of zonder een detailbericht:

  • Throwable () maakt een Throwablebericht zonder detail. Deze constructor is geschikt voor situaties waarin er geen context is. U wilt bijvoorbeeld alleen weten dat een stapel leeg of vol is.
  • Throwable (String-bericht) maakt een Throwablemet messageals het detailbericht . Dit bericht kan naar de gebruiker worden gestuurd en / of worden gelogd.

Throwablebiedt de String getMessage()methode om het detailbericht te retourneren. Het biedt ook aanvullende handige methoden, die ik later zal introduceren.

De uitzondering klasse

Throwableheeft twee directe subklassen. Een van deze subklassen is Exception, die een uitzondering beschrijft die voortkomt uit een externe factor (zoals een poging om te lezen uit een niet-bestaand bestand). Exceptiondeclareert dezelfde constructors (met identieke parameterlijsten) als Throwable, en elke constructor roept zijn Throwabletegenhanger aan. Exceptionerft Throwablede methoden van; het verklaart geen nieuwe methoden.

Java biedt veel uitzonderingsklassen die direct onderklassen Exception. Hier zijn drie voorbeelden:

  • CloneNotSupportedException signaleert een poging om een ​​object te klonen waarvan de klasse de Cloneableinterface niet implementeert . Beide typen zitten in de java.langverpakking.
  • IOException geeft aan dat er een soort I / O-fout is opgetreden. Dit type bevindt zich in de java.ioverpakking.
  • ParseException geeft aan dat er een fout is opgetreden tijdens het parseren van tekst. Dit type is te vinden in de java.textverpakking.

Merk op dat de Exceptionnaam van elke subklasse eindigt met het woord Exception. Deze conventie maakt het gemakkelijk om het doel van de klas te identificeren.

U zult meestal een subklasse Exception(of een van de subklassen) maken met uw eigen uitzonderingsklassen (waarvan de naam moet eindigen op Exception). Hier zijn een paar voorbeelden van aangepaste subklassen:

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

Het eerste voorbeeld beschrijft een uitzonderingsklasse waarvoor geen detailbericht vereist is. Het is standaard noargument constructor aanroept Exception(), die aanroept Throwable().

Het tweede voorbeeld beschrijft een uitzonderingsklasse waarvan de constructor een detailbericht en de naam van de lege map vereist. De constructor roept Exception(String message), die aanroept Throwable(String message).

Objecten die zijn geïnstantieerd uit Exceptionof een van de subklassen (met uitzondering van RuntimeExceptionof een van de subklassen) zijn gecontroleerde uitzonderingen.

De klasse RuntimeException

Exceptionwordt direct onderverdeeld in RuntimeException, wat een uitzondering beschrijft die hoogstwaarschijnlijk het gevolg is van slecht geschreven code. RuntimeExceptiondeclareert dezelfde constructors (met identieke parameterlijsten) als Exception, en elke constructor roept zijn Exceptiontegenhanger aan. RuntimeExceptionerft Throwablede methoden van. Het verklaart geen nieuwe methoden.

Java biedt veel uitzonderingsklassen die direct onderklassen RuntimeException. De volgende voorbeelden zijn allemaal leden van het java.langpakket:

  • ArithmeticException signaleert een ongeldige rekenkundige bewerking, zoals een poging om een ​​geheel getal te delen door 0.
  • IllegalArgumentException geeft aan dat een illegaal of ongepast argument aan een methode is doorgegeven.
  • NullPointerException signaleert een poging om een ​​methode aan te roepen of toegang te krijgen tot een instantieveld via de null-referentie.

Objecten die zijn geïnstantieerd uit RuntimeExceptionof een van de subklassen ervan, zijn ongecontroleerde uitzonderingen .

De Error-klasse

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.